@peers-app/peers-ui 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/.github/workflows/publish.yml +8 -5
  2. package/babel.config.js +4 -4
  3. package/biome.json +191 -0
  4. package/dist/command-palette/command-palette-ui.d.ts +1 -2
  5. package/dist/command-palette/command-palette-ui.js +175 -244
  6. package/dist/command-palette/command-palette.js +65 -64
  7. package/dist/components/chat-overlay.d.ts +2 -2
  8. package/dist/components/chat-overlay.js +160 -217
  9. package/dist/components/checkbox.d.ts +5 -4
  10. package/dist/components/checkbox.js +4 -7
  11. package/dist/components/group-switcher.d.ts +1 -2
  12. package/dist/components/group-switcher.js +119 -159
  13. package/dist/components/input-date.d.ts +3 -3
  14. package/dist/components/input-date.js +6 -6
  15. package/dist/components/input-number.d.ts +7 -6
  16. package/dist/components/input-number.js +25 -20
  17. package/dist/components/input.d.ts +5 -4
  18. package/dist/components/input.js +4 -7
  19. package/dist/components/inverse-lazy-list.d.ts +3 -3
  20. package/dist/components/inverse-lazy-list.js +13 -47
  21. package/dist/components/io-schema-values.d.ts +5 -6
  22. package/dist/components/io-schema-values.js +28 -65
  23. package/dist/components/io-schema.d.ts +4 -5
  24. package/dist/components/io-schema.js +42 -79
  25. package/dist/components/lazy-list.d.ts +3 -3
  26. package/dist/components/lazy-list.js +38 -58
  27. package/dist/components/list-screen.d.ts +3 -8
  28. package/dist/components/list-screen.js +28 -23
  29. package/dist/components/loading-indicator.d.ts +1 -2
  30. package/dist/components/loading-indicator.js +2 -6
  31. package/dist/components/markdown-editor/autolink-plugin.js +5 -8
  32. package/dist/components/markdown-editor/editor-inline.d.ts +2 -3
  33. package/dist/components/markdown-editor/editor-inline.js +2 -6
  34. package/dist/components/markdown-editor/editor.d.ts +6 -6
  35. package/dist/components/markdown-editor/editor.js +9 -19
  36. package/dist/components/markdown-editor/markdown-plugin.d.ts +1 -1
  37. package/dist/components/markdown-editor/markdown-plugin.js +20 -21
  38. package/dist/components/markdown-editor/mention-node.d.ts +2 -2
  39. package/dist/components/markdown-editor/mention-node.js +24 -24
  40. package/dist/components/markdown-editor/mentions-plugin.d.ts +2 -2
  41. package/dist/components/markdown-editor/mentions-plugin.js +61 -62
  42. package/dist/components/markdown-editor/theme.js +28 -28
  43. package/dist/components/markdown-editor/toolbar.d.ts +2 -3
  44. package/dist/components/markdown-editor/toolbar.js +32 -49
  45. package/dist/components/markdown-with-mentions.d.ts +1 -1
  46. package/dist/components/markdown-with-mentions.js +43 -43
  47. package/dist/components/message-logs/message-logs.d.ts +1 -2
  48. package/dist/components/message-logs/message-logs.js +91 -116
  49. package/dist/components/messages/avatar.d.ts +3 -4
  50. package/dist/components/messages/avatar.js +37 -46
  51. package/dist/components/messages/channel-message-list.d.ts +5 -6
  52. package/dist/components/messages/channel-message-list.js +34 -34
  53. package/dist/components/messages/channel-view.d.ts +1 -2
  54. package/dist/components/messages/channel-view.js +23 -57
  55. package/dist/components/messages/message-compose.d.ts +3 -4
  56. package/dist/components/messages/message-compose.js +27 -38
  57. package/dist/components/messages/message-display.d.ts +2 -3
  58. package/dist/components/messages/message-display.js +42 -95
  59. package/dist/components/messages/thread-message-list.d.ts +4 -5
  60. package/dist/components/messages/thread-message-list.js +29 -29
  61. package/dist/components/router.d.ts +1 -2
  62. package/dist/components/router.js +58 -66
  63. package/dist/components/save-button.d.ts +3 -3
  64. package/dist/components/save-button.js +23 -33
  65. package/dist/components/sortable-list.d.ts +11 -12
  66. package/dist/components/sortable-list.js +42 -22
  67. package/dist/components/tabs.d.ts +3 -3
  68. package/dist/components/tabs.js +16 -47
  69. package/dist/components/tooltip.d.ts +4 -4
  70. package/dist/components/tooltip.js +4 -9
  71. package/dist/components/trust-level-badge.d.ts +2 -3
  72. package/dist/components/trust-level-badge.js +16 -16
  73. package/dist/components/trust-level-dropdown.d.ts +3 -4
  74. package/dist/components/trust-level-dropdown.js +32 -55
  75. package/dist/components/typeahead.d.ts +3 -3
  76. package/dist/components/typeahead.js +48 -89
  77. package/dist/components/voice-indicator.d.ts +2 -2
  78. package/dist/components/voice-indicator.js +93 -106
  79. package/dist/components/voice-subscribe-events.d.ts +32 -0
  80. package/dist/components/voice-subscribe-events.js +2 -0
  81. package/dist/globals.d.ts +3 -5
  82. package/dist/globals.js +22 -33
  83. package/dist/hooks.d.ts +5 -5
  84. package/dist/hooks.js +22 -12
  85. package/dist/hooks.test.js +129 -145
  86. package/dist/index.d.ts +9 -8
  87. package/dist/index.js +13 -12
  88. package/dist/mention-configs.d.ts +2 -2
  89. package/dist/mention-configs.js +55 -42
  90. package/dist/screens/assistants/assistant-config.d.ts +2 -3
  91. package/dist/screens/assistants/assistant-config.js +9 -22
  92. package/dist/screens/assistants/assistant-details.d.ts +1 -2
  93. package/dist/screens/assistants/assistant-details.js +13 -28
  94. package/dist/screens/assistants/assistant-info.d.ts +2 -3
  95. package/dist/screens/assistants/assistant-info.js +3 -17
  96. package/dist/screens/assistants/assistant-list.d.ts +1 -2
  97. package/dist/screens/assistants/assistant-list.js +15 -56
  98. package/dist/screens/assistants/assistant-tools.d.ts +2 -3
  99. package/dist/screens/assistants/assistant-tools.js +10 -24
  100. package/dist/screens/console-logs/console-logs-list.d.ts +1 -2
  101. package/dist/screens/console-logs/console-logs-list.js +130 -134
  102. package/dist/screens/console-logs/log-display.d.ts +2 -3
  103. package/dist/screens/console-logs/log-display.js +40 -42
  104. package/dist/screens/console-logs/log-filters.d.ts +1 -2
  105. package/dist/screens/console-logs/log-filters.js +2 -24
  106. package/dist/screens/console-logs/mobile-log-card.d.ts +2 -3
  107. package/dist/screens/console-logs/mobile-log-card.js +64 -67
  108. package/dist/screens/console-logs/resizable-table-header.d.ts +1 -2
  109. package/dist/screens/console-logs/resizable-table-header.js +31 -67
  110. package/dist/screens/contacts/contact-details.d.ts +1 -2
  111. package/dist/screens/contacts/contact-details.js +16 -46
  112. package/dist/screens/contacts/contact-list.d.ts +1 -2
  113. package/dist/screens/contacts/contact-list.js +44 -103
  114. package/dist/screens/contacts/index.d.ts +4 -4
  115. package/dist/screens/contacts/index.js +1 -1
  116. package/dist/screens/contacts/user-connect.d.ts +1 -2
  117. package/dist/screens/contacts/user-connect.js +85 -186
  118. package/dist/screens/data-explorer/data-explorer.d.ts +1 -2
  119. package/dist/screens/data-explorer/data-explorer.js +61 -181
  120. package/dist/screens/data-explorer/index.d.ts +2 -2
  121. package/dist/screens/data-explorer/query-executor.d.ts +1 -2
  122. package/dist/screens/data-explorer/query-executor.js +56 -166
  123. package/dist/screens/groups/group-details.d.ts +1 -2
  124. package/dist/screens/groups/group-details.js +27 -122
  125. package/dist/screens/groups/group-invite-listener.d.ts +2 -3
  126. package/dist/screens/groups/group-invite-listener.js +8 -104
  127. package/dist/screens/groups/group-list.d.ts +1 -2
  128. package/dist/screens/groups/group-list.js +56 -133
  129. package/dist/screens/groups/group-members.d.ts +2 -3
  130. package/dist/screens/groups/group-members.js +62 -132
  131. package/dist/screens/groups/index.d.ts +4 -4
  132. package/dist/screens/groups/index.js +1 -1
  133. package/dist/screens/join-group/index.d.ts +2 -2
  134. package/dist/screens/join-group/join-group.d.ts +1 -2
  135. package/dist/screens/join-group/join-group.js +9 -109
  136. package/dist/screens/network-viewer/connection-troubleshooter.d.ts +2 -3
  137. package/dist/screens/network-viewer/connection-troubleshooter.js +89 -193
  138. package/dist/screens/network-viewer/cpu-usage-graph.d.ts +1 -2
  139. package/dist/screens/network-viewer/cpu-usage-graph.js +60 -99
  140. package/dist/screens/network-viewer/device-details-modal.d.ts +1 -2
  141. package/dist/screens/network-viewer/device-details-modal.js +25 -177
  142. package/dist/screens/network-viewer/group-details-modal.d.ts +1 -2
  143. package/dist/screens/network-viewer/group-details-modal.js +31 -142
  144. package/dist/screens/network-viewer/index.d.ts +4 -4
  145. package/dist/screens/network-viewer/index.js +3 -3
  146. package/dist/screens/network-viewer/network-viewer-ipc.d.ts +22 -0
  147. package/dist/screens/network-viewer/network-viewer-ipc.js +6 -0
  148. package/dist/screens/network-viewer/network-viewer.d.ts +1 -2
  149. package/dist/screens/network-viewer/network-viewer.js +91 -296
  150. package/dist/screens/network-viewer/usage-graph.d.ts +1 -2
  151. package/dist/screens/network-viewer/usage-graph.js +78 -110
  152. package/dist/screens/packages/package-details.d.ts +1 -2
  153. package/dist/screens/packages/package-details.js +35 -41
  154. package/dist/screens/packages/package-info.d.ts +2 -2
  155. package/dist/screens/packages/package-info.js +33 -86
  156. package/dist/screens/packages/package-list.d.ts +1 -2
  157. package/dist/screens/packages/package-list.js +42 -106
  158. package/dist/screens/packages/package-new-local.d.ts +1 -2
  159. package/dist/screens/packages/package-new-local.js +13 -19
  160. package/dist/screens/packages/package-versions.d.ts +2 -3
  161. package/dist/screens/packages/package-versions.js +29 -96
  162. package/dist/screens/peer-types/peer-type-details.d.ts +3 -4
  163. package/dist/screens/peer-types/peer-type-details.js +26 -78
  164. package/dist/screens/peer-types/peer-type-list.d.ts +1 -2
  165. package/dist/screens/peer-types/peer-type-list.js +13 -24
  166. package/dist/screens/search/global-search.d.ts +1 -2
  167. package/dist/screens/search/global-search.js +104 -182
  168. package/dist/screens/settings/color-mode-dropdown.d.ts +3 -4
  169. package/dist/screens/settings/color-mode-dropdown.js +18 -37
  170. package/dist/screens/settings/settings-page.d.ts +1 -1
  171. package/dist/screens/settings/settings-page.js +86 -213
  172. package/dist/screens/settings/voice-settings-agent.d.ts +1 -1
  173. package/dist/screens/settings/voice-settings-agent.js +7 -44
  174. package/dist/screens/settings/voice-settings-api-keys.d.ts +2 -2
  175. package/dist/screens/settings/voice-settings-api-keys.js +2 -29
  176. package/dist/screens/settings/voice-settings-output.d.ts +2 -2
  177. package/dist/screens/settings/voice-settings-output.js +2 -40
  178. package/dist/screens/settings/voice-settings-providers.d.ts +2 -2
  179. package/dist/screens/settings/voice-settings-providers.js +2 -19
  180. package/dist/screens/settings/voice-settings-types.d.ts +4 -4
  181. package/dist/screens/settings/voice-settings-types.js +31 -31
  182. package/dist/screens/settings/voice-settings-wake-word.d.ts +2 -2
  183. package/dist/screens/settings/voice-settings-wake-word.js +2 -33
  184. package/dist/screens/settings/voice-settings.d.ts +1 -1
  185. package/dist/screens/settings/voice-settings.js +35 -112
  186. package/dist/screens/setup-user.d.ts +1 -2
  187. package/dist/screens/setup-user.js +38 -116
  188. package/dist/screens/tools/tool-code.d.ts +2 -3
  189. package/dist/screens/tools/tool-code.js +9 -13
  190. package/dist/screens/tools/tool-details.d.ts +1 -2
  191. package/dist/screens/tools/tool-details.js +26 -39
  192. package/dist/screens/tools/tool-info.d.ts +2 -3
  193. package/dist/screens/tools/tool-info.js +9 -48
  194. package/dist/screens/tools/tool-list.d.ts +1 -2
  195. package/dist/screens/tools/tool-list.js +33 -65
  196. package/dist/screens/tools/tool-schema.d.ts +2 -3
  197. package/dist/screens/tools/tool-schema.js +2 -13
  198. package/dist/screens/tools/tool-test-details.d.ts +1 -2
  199. package/dist/screens/tools/tool-test-details.js +12 -28
  200. package/dist/screens/tools/tool-test-list.d.ts +1 -2
  201. package/dist/screens/tools/tool-test-list.js +17 -56
  202. package/dist/screens/variables/variable-details.d.ts +1 -2
  203. package/dist/screens/variables/variable-details.js +19 -86
  204. package/dist/screens/variables/variable-list.d.ts +1 -2
  205. package/dist/screens/variables/variable-list.js +16 -27
  206. package/dist/screens/welcome-modal.d.ts +1 -2
  207. package/dist/screens/welcome-modal.js +44 -111
  208. package/dist/screens/workflows/workflow-details.d.ts +1 -2
  209. package/dist/screens/workflows/workflow-details.js +17 -31
  210. package/dist/screens/workflows/workflow-info.d.ts +2 -3
  211. package/dist/screens/workflows/workflow-info.js +2 -9
  212. package/dist/screens/workflows/workflow-instructions.d.ts +2 -3
  213. package/dist/screens/workflows/workflow-instructions.js +23 -55
  214. package/dist/screens/workflows/workflow-list.d.ts +1 -2
  215. package/dist/screens/workflows/workflow-list.js +23 -62
  216. package/dist/setupTests.d.ts +1 -1
  217. package/dist/setupTests.js +10 -11
  218. package/dist/system-apps/assistants.app.d.ts +1 -1
  219. package/dist/system-apps/assistants.app.js +3 -3
  220. package/dist/system-apps/console-logs.app.d.ts +1 -1
  221. package/dist/system-apps/console-logs.app.js +3 -3
  222. package/dist/system-apps/contacts.app.d.ts +1 -1
  223. package/dist/system-apps/contacts.app.js +4 -4
  224. package/dist/system-apps/data-explorer.app.d.ts +1 -1
  225. package/dist/system-apps/data-explorer.app.js +4 -4
  226. package/dist/system-apps/groups.app.d.ts +1 -1
  227. package/dist/system-apps/groups.app.js +4 -4
  228. package/dist/system-apps/index.d.ts +17 -17
  229. package/dist/system-apps/index.js +52 -52
  230. package/dist/system-apps/join-group.app.d.ts +1 -1
  231. package/dist/system-apps/join-group.app.js +4 -4
  232. package/dist/system-apps/mobile-settings.app.d.ts +1 -1
  233. package/dist/system-apps/mobile-settings.app.js +3 -3
  234. package/dist/system-apps/network-viewer.app.d.ts +1 -1
  235. package/dist/system-apps/network-viewer.app.js +4 -4
  236. package/dist/system-apps/packages.app.d.ts +1 -1
  237. package/dist/system-apps/packages.app.js +3 -3
  238. package/dist/system-apps/search.app.d.ts +1 -1
  239. package/dist/system-apps/search.app.js +4 -4
  240. package/dist/system-apps/settings.app.d.ts +1 -1
  241. package/dist/system-apps/settings.app.js +3 -3
  242. package/dist/system-apps/threads.app.d.ts +1 -1
  243. package/dist/system-apps/threads.app.js +3 -3
  244. package/dist/system-apps/tools.app.d.ts +1 -1
  245. package/dist/system-apps/tools.app.js +3 -3
  246. package/dist/system-apps/types.app.d.ts +1 -1
  247. package/dist/system-apps/types.app.js +3 -3
  248. package/dist/system-apps/variables.app.d.ts +1 -1
  249. package/dist/system-apps/variables.app.js +3 -3
  250. package/dist/system-apps/workflows.app.d.ts +1 -1
  251. package/dist/system-apps/workflows.app.js +3 -3
  252. package/dist/tabs-layout/tabs-layout.d.ts +2 -3
  253. package/dist/tabs-layout/tabs-layout.js +215 -246
  254. package/dist/tabs-layout/tabs-state.d.ts +2 -2
  255. package/dist/tabs-layout/tabs-state.js +73 -61
  256. package/dist/ui-defaults/index.d.ts +2 -2
  257. package/dist/ui-defaults/list-screen.d.ts +2 -3
  258. package/dist/ui-defaults/list-screen.js +33 -37
  259. package/dist/ui-defaults/markdown-field.js +24 -56
  260. package/dist/ui-router/routes-loader.d.ts +1 -1
  261. package/dist/ui-router/routes-loader.js +17 -13
  262. package/dist/ui-router/ui-loader.d.ts +6 -6
  263. package/dist/ui-router/ui-loader.js +172 -268
  264. package/dist/utils.js +49 -39
  265. package/jest.config.js +16 -16
  266. package/package.json +16 -14
  267. package/src/command-palette/command-palette-ui.tsx +261 -237
  268. package/src/command-palette/command-palette.ts +81 -78
  269. package/src/components/chat-overlay.tsx +366 -261
  270. package/src/components/checkbox.tsx +15 -12
  271. package/src/components/group-switcher.tsx +150 -105
  272. package/src/components/input-date.tsx +17 -16
  273. package/src/components/input-number.tsx +47 -31
  274. package/src/components/input.tsx +15 -12
  275. package/src/components/inverse-lazy-list.tsx +14 -13
  276. package/src/components/io-schema-values.tsx +51 -69
  277. package/src/components/io-schema.tsx +94 -69
  278. package/src/components/lazy-list.tsx +51 -34
  279. package/src/components/list-screen.tsx +51 -35
  280. package/src/components/loading-indicator.tsx +2 -4
  281. package/src/components/markdown-editor/autolink-plugin.tsx +4 -11
  282. package/src/components/markdown-editor/editor-inline.tsx +3 -4
  283. package/src/components/markdown-editor/editor.tsx +53 -51
  284. package/src/components/markdown-editor/markdown-plugin.tsx +48 -40
  285. package/src/components/markdown-editor/mention-node.ts +39 -38
  286. package/src/components/markdown-editor/mentions-plugin.tsx +99 -101
  287. package/src/components/markdown-editor/theme.ts +28 -29
  288. package/src/components/markdown-editor/toolbar.tsx +53 -47
  289. package/src/components/markdown-with-mentions.tsx +56 -46
  290. package/src/components/message-logs/message-logs.tsx +225 -165
  291. package/src/components/messages/avatar.tsx +70 -52
  292. package/src/components/messages/channel-message-list.tsx +80 -68
  293. package/src/components/messages/channel-view.tsx +34 -33
  294. package/src/components/messages/message-compose.tsx +84 -67
  295. package/src/components/messages/message-display.tsx +103 -89
  296. package/src/components/messages/thread-message-list.tsx +53 -44
  297. package/src/components/router.tsx +42 -43
  298. package/src/components/save-button.tsx +43 -39
  299. package/src/components/sortable-list.tsx +77 -49
  300. package/src/components/tabs.tsx +31 -31
  301. package/src/components/tooltip.tsx +21 -28
  302. package/src/components/trust-level-badge.tsx +15 -11
  303. package/src/components/trust-level-dropdown.tsx +49 -19
  304. package/src/components/typeahead.tsx +57 -59
  305. package/src/components/voice-indicator.tsx +158 -141
  306. package/src/components/voice-subscribe-events.ts +20 -0
  307. package/src/globals.tsx +42 -40
  308. package/src/hooks.test.tsx +141 -134
  309. package/src/hooks.ts +80 -48
  310. package/src/index.tsx +17 -10
  311. package/src/mention-configs.ts +122 -68
  312. package/src/screens/assistants/assistant-config.tsx +28 -18
  313. package/src/screens/assistants/assistant-details.tsx +35 -36
  314. package/src/screens/assistants/assistant-info.tsx +16 -11
  315. package/src/screens/assistants/assistant-list.tsx +37 -34
  316. package/src/screens/assistants/assistant-tools.tsx +41 -20
  317. package/src/screens/console-logs/console-logs-list.tsx +173 -140
  318. package/src/screens/console-logs/log-display.tsx +65 -38
  319. package/src/screens/console-logs/log-filters.tsx +4 -3
  320. package/src/screens/console-logs/mobile-log-card.tsx +78 -71
  321. package/src/screens/console-logs/resizable-table-header.tsx +29 -21
  322. package/src/screens/contacts/contact-details.tsx +29 -30
  323. package/src/screens/contacts/contact-list.tsx +71 -60
  324. package/src/screens/contacts/index.ts +5 -5
  325. package/src/screens/contacts/user-connect.tsx +177 -171
  326. package/src/screens/data-explorer/data-explorer.tsx +134 -98
  327. package/src/screens/data-explorer/index.ts +2 -3
  328. package/src/screens/data-explorer/query-executor.tsx +90 -80
  329. package/src/screens/groups/group-details.tsx +120 -101
  330. package/src/screens/groups/group-invite-listener.tsx +34 -37
  331. package/src/screens/groups/group-list.tsx +119 -103
  332. package/src/screens/groups/group-members.tsx +225 -164
  333. package/src/screens/groups/index.ts +5 -6
  334. package/src/screens/join-group/index.ts +2 -2
  335. package/src/screens/join-group/join-group.tsx +41 -39
  336. package/src/screens/network-viewer/connection-troubleshooter.tsx +145 -104
  337. package/src/screens/network-viewer/cpu-usage-graph.tsx +39 -43
  338. package/src/screens/network-viewer/device-details-modal.tsx +46 -59
  339. package/src/screens/network-viewer/group-details-modal.tsx +68 -49
  340. package/src/screens/network-viewer/index.ts +4 -5
  341. package/src/screens/network-viewer/network-viewer-ipc.ts +23 -0
  342. package/src/screens/network-viewer/network-viewer.tsx +261 -236
  343. package/src/screens/network-viewer/usage-graph.tsx +57 -49
  344. package/src/screens/packages/package-details.tsx +43 -35
  345. package/src/screens/packages/package-info.tsx +107 -66
  346. package/src/screens/packages/package-list.tsx +175 -98
  347. package/src/screens/packages/package-new-local.tsx +28 -26
  348. package/src/screens/packages/package-versions.tsx +102 -77
  349. package/src/screens/peer-types/peer-type-details.tsx +60 -50
  350. package/src/screens/peer-types/peer-type-list.tsx +20 -30
  351. package/src/screens/search/global-search.tsx +153 -137
  352. package/src/screens/settings/color-mode-dropdown.tsx +52 -35
  353. package/src/screens/settings/settings-page.tsx +215 -141
  354. package/src/screens/settings/voice-settings-agent.tsx +13 -12
  355. package/src/screens/settings/voice-settings-api-keys.tsx +14 -12
  356. package/src/screens/settings/voice-settings-output.tsx +12 -11
  357. package/src/screens/settings/voice-settings-providers.tsx +7 -3
  358. package/src/screens/settings/voice-settings-types.ts +52 -49
  359. package/src/screens/settings/voice-settings-wake-word.tsx +25 -9
  360. package/src/screens/settings/voice-settings.tsx +66 -43
  361. package/src/screens/setup-user.tsx +88 -41
  362. package/src/screens/tools/tool-code.tsx +12 -17
  363. package/src/screens/tools/tool-details.tsx +28 -28
  364. package/src/screens/tools/tool-info.tsx +14 -19
  365. package/src/screens/tools/tool-list.tsx +58 -40
  366. package/src/screens/tools/tool-schema.tsx +16 -9
  367. package/src/screens/tools/tool-test-details.tsx +11 -22
  368. package/src/screens/tools/tool-test-list.tsx +29 -30
  369. package/src/screens/variables/variable-details.tsx +63 -51
  370. package/src/screens/variables/variable-list.tsx +29 -30
  371. package/src/screens/welcome-modal.tsx +68 -48
  372. package/src/screens/workflows/workflow-details.tsx +40 -30
  373. package/src/screens/workflows/workflow-info.tsx +4 -11
  374. package/src/screens/workflows/workflow-instructions.tsx +35 -28
  375. package/src/screens/workflows/workflow-list.tsx +50 -40
  376. package/src/setupTests.ts +14 -13
  377. package/src/system-apps/assistants.app.ts +5 -5
  378. package/src/system-apps/console-logs.app.ts +4 -4
  379. package/src/system-apps/contacts.app.ts +6 -6
  380. package/src/system-apps/data-explorer.app.ts +5 -5
  381. package/src/system-apps/groups.app.ts +6 -6
  382. package/src/system-apps/index.ts +49 -49
  383. package/src/system-apps/join-group.app.ts +5 -5
  384. package/src/system-apps/mobile-settings.app.ts +4 -5
  385. package/src/system-apps/network-viewer.app.ts +5 -5
  386. package/src/system-apps/packages.app.ts +5 -5
  387. package/src/system-apps/search.app.ts +6 -6
  388. package/src/system-apps/settings.app.ts +5 -5
  389. package/src/system-apps/threads.app.ts +5 -5
  390. package/src/system-apps/tools.app.ts +5 -5
  391. package/src/system-apps/types.app.ts +5 -5
  392. package/src/system-apps/variables.app.ts +5 -5
  393. package/src/system-apps/workflows.app.ts +5 -5
  394. package/src/tabs-layout/tabs-layout.tsx +345 -254
  395. package/src/tabs-layout/tabs-state.ts +100 -81
  396. package/src/ui-defaults/index.ts +2 -3
  397. package/src/ui-defaults/list-screen.tsx +45 -40
  398. package/src/ui-defaults/markdown-field.tsx +22 -26
  399. package/src/ui-router/routes-loader.ts +40 -24
  400. package/src/ui-router/ui-loader.tsx +312 -214
  401. package/src/utils.ts +68 -81
  402. package/tsconfig.json +5 -10
  403. package/dist/components/input-datetime.d.ts +0 -7
  404. package/dist/components/input-datetime.js +0 -35
  405. package/dist/components/lazy-sortable-list.d.ts +0 -29
  406. package/dist/components/lazy-sortable-list.js +0 -12
  407. package/dist/components/left-bar.d.ts +0 -5
  408. package/dist/components/left-bar.js +0 -207
  409. package/dist/components/main-content-container.d.ts +0 -2
  410. package/dist/components/main-content-container.js +0 -92
  411. package/dist/components/messages/thread-view.d.ts +0 -6
  412. package/dist/components/messages/thread-view.js +0 -174
  413. package/dist/components/off-canvas.d.ts +0 -13
  414. package/dist/components/off-canvas.js +0 -89
  415. package/dist/components/text-list-editor.tsx/text-list-editor.d.ts +0 -6
  416. package/dist/components/text-list-editor.tsx/text-list-editor.js +0 -13
  417. package/dist/components/top-bar.d.ts +0 -2
  418. package/dist/components/top-bar.js +0 -51
  419. package/dist/components/typeahead/mentions-plugin.d.ts +0 -7
  420. package/dist/components/typeahead/mentions-plugin.js +0 -203
  421. package/dist/components/typeahead/typeahead-editor.d.ts +0 -15
  422. package/dist/components/typeahead/typeahead-editor.js +0 -134
  423. package/dist/components/typeahead/typeahead.d.ts +0 -12
  424. package/dist/components/typeahead/typeahead.js +0 -94
  425. package/dist/screens/profile.d.ts +0 -2
  426. package/dist/screens/profile.js +0 -76
  427. package/src/components/input-datetime.tsx +0 -41
  428. package/src/components/lazy-sortable-list.tsx +0 -51
  429. package/src/components/left-bar.tsx +0 -322
  430. package/src/components/main-content-container.tsx +0 -79
  431. package/src/components/messages/thread-view.tsx +0 -214
  432. package/src/components/off-canvas.tsx +0 -83
  433. package/src/components/text-list-editor.tsx/text-list-editor.tsx +0 -13
  434. package/src/components/top-bar.tsx +0 -119
  435. package/src/components/typeahead/mentions-plugin.tsx +0 -265
  436. package/src/components/typeahead/typeahead-editor.tsx +0 -140
  437. package/src/components/typeahead/typeahead.tsx +0 -77
  438. package/src/screens/profile.tsx +0 -75
@@ -1,15 +1,16 @@
1
- import React from 'react';
2
- import { render, screen, act, waitFor } from '@testing-library/react';
3
- import { observable, Observable } from '@peers-app/peers-sdk';
4
- import { useObservable, useObservableState } from './hooks';
1
+ import { type Observable, observable } from "@peers-app/peers-sdk";
2
+ import { act, render, screen, waitFor } from "@testing-library/react";
3
+ import { useObservable, useObservableState } from "./hooks";
5
4
 
6
5
  // Helper component to test useObservable
7
- function TestComponent<T>({ obs, testId = 'value' }: { obs: Observable<T> | T, testId?: string }) {
6
+ function TestComponent<T>({ obs, testId = "value" }: { obs: Observable<T> | T; testId?: string }) {
8
7
  const [value, setValue] = useObservable(obs);
9
8
  return (
10
9
  <div>
11
10
  <span data-testid={testId}>{JSON.stringify(value)}</span>
12
- <button data-testid="setter" onClick={() => setValue('new-value' as any)}>Set</button>
11
+ <button type="button" data-testid="setter" onClick={() => setValue("new-value" as T)}>
12
+ Set
13
+ </button>
13
14
  </div>
14
15
  );
15
16
  }
@@ -32,108 +33,110 @@ function ObservableStateTestComponent({ initialValue }: { initialValue: number }
32
33
  return (
33
34
  <div>
34
35
  <span data-testid="state-value">{value}</span>
35
- <button data-testid="increment" onClick={() => obs(obs() + 1)}>Increment</button>
36
+ <button type="button" data-testid="increment" onClick={() => obs(obs() + 1)}>
37
+ Increment
38
+ </button>
36
39
  </div>
37
40
  );
38
41
  }
39
42
 
40
- describe('useObservable', () => {
41
- describe('basic functionality', () => {
42
- it('should return the current value of an observable', () => {
43
- const obs = observable('hello');
43
+ describe("useObservable", () => {
44
+ describe("basic functionality", () => {
45
+ it("should return the current value of an observable", () => {
46
+ const obs = observable("hello");
44
47
  render(<TestComponent obs={obs} />);
45
- expect(screen.getByTestId('value')).toHaveTextContent('"hello"');
48
+ expect(screen.getByTestId("value")).toHaveTextContent('"hello"');
46
49
  });
47
50
 
48
- it('should return the value when passed a plain value (non-observable)', () => {
51
+ it("should return the value when passed a plain value (non-observable)", () => {
49
52
  render(<TestComponent obs="plain-value" />);
50
- expect(screen.getByTestId('value')).toHaveTextContent('"plain-value"');
53
+ expect(screen.getByTestId("value")).toHaveTextContent('"plain-value"');
51
54
  });
52
55
 
53
- it('should re-render when observable value changes', async () => {
54
- const obs = observable('initial');
56
+ it("should re-render when observable value changes", async () => {
57
+ const obs = observable("initial");
55
58
  render(<TestComponent obs={obs} />);
56
-
57
- expect(screen.getByTestId('value')).toHaveTextContent('"initial"');
58
-
59
+
60
+ expect(screen.getByTestId("value")).toHaveTextContent('"initial"');
61
+
59
62
  act(() => {
60
- obs('updated');
63
+ obs("updated");
61
64
  });
62
-
65
+
63
66
  await waitFor(() => {
64
- expect(screen.getByTestId('value')).toHaveTextContent('"updated"');
67
+ expect(screen.getByTestId("value")).toHaveTextContent('"updated"');
65
68
  });
66
69
  });
67
70
 
68
- it('should update the observable when setter is called', async () => {
69
- const obs = observable('initial');
71
+ it("should update the observable when setter is called", async () => {
72
+ const obs = observable("initial");
70
73
  render(<TestComponent obs={obs} />);
71
-
74
+
72
75
  act(() => {
73
- screen.getByTestId('setter').click();
76
+ screen.getByTestId("setter").click();
74
77
  });
75
-
78
+
76
79
  await waitFor(() => {
77
- expect(obs()).toBe('new-value');
78
- expect(screen.getByTestId('value')).toHaveTextContent('"new-value"');
80
+ expect(obs()).toBe("new-value");
81
+ expect(screen.getByTestId("value")).toHaveTextContent('"new-value"');
79
82
  });
80
83
  });
81
84
 
82
- it('should handle undefined values', () => {
85
+ it("should handle undefined values", () => {
83
86
  const obs = observable<string | undefined>(undefined);
84
87
  render(<TestComponent obs={obs} />);
85
88
  // undefined serializes to empty string in JSON.stringify, but actually shows as nothing
86
- expect(screen.getByTestId('value')).toHaveTextContent('');
89
+ expect(screen.getByTestId("value")).toHaveTextContent("");
87
90
  });
88
91
 
89
- it('should handle object values', async () => {
90
- const obs = observable({ name: 'test', count: 5 });
92
+ it("should handle object values", async () => {
93
+ const obs = observable({ name: "test", count: 5 });
91
94
  render(<TestComponent obs={obs} />);
92
-
93
- expect(screen.getByTestId('value')).toHaveTextContent('{"name":"test","count":5}');
94
-
95
+
96
+ expect(screen.getByTestId("value")).toHaveTextContent('{"name":"test","count":5}');
97
+
95
98
  act(() => {
96
- obs({ name: 'updated', count: 10 });
99
+ obs({ name: "updated", count: 10 });
97
100
  });
98
-
101
+
99
102
  await waitFor(() => {
100
- expect(screen.getByTestId('value')).toHaveTextContent('{"name":"updated","count":10}');
103
+ expect(screen.getByTestId("value")).toHaveTextContent('{"name":"updated","count":10}');
101
104
  });
102
105
  });
103
106
  });
104
107
 
105
- describe('array handling', () => {
106
- it('should handle array values', () => {
107
- const obs = observable(['a', 'b', 'c']);
108
+ describe("array handling", () => {
109
+ it("should handle array values", () => {
110
+ const obs = observable(["a", "b", "c"]);
108
111
  render(<ArrayTestComponent obs={obs} />);
109
-
110
- expect(screen.getByTestId('array-value')).toHaveTextContent('["a","b","c"]');
111
- expect(screen.getByTestId('array-length')).toHaveTextContent('3');
112
+
113
+ expect(screen.getByTestId("array-value")).toHaveTextContent('["a","b","c"]');
114
+ expect(screen.getByTestId("array-length")).toHaveTextContent("3");
112
115
  });
113
116
 
114
- it('should re-render when array is replaced with new reference', async () => {
115
- const obs = observable(['a', 'b']);
117
+ it("should re-render when array is replaced with new reference", async () => {
118
+ const obs = observable(["a", "b"]);
116
119
  render(<ArrayTestComponent obs={obs} />);
117
-
118
- expect(screen.getByTestId('array-length')).toHaveTextContent('2');
119
-
120
+
121
+ expect(screen.getByTestId("array-length")).toHaveTextContent("2");
122
+
120
123
  act(() => {
121
- obs(['a', 'b', 'c']); // New array reference
124
+ obs(["a", "b", "c"]); // New array reference
122
125
  });
123
-
126
+
124
127
  await waitFor(() => {
125
- expect(screen.getByTestId('array-length')).toHaveTextContent('3');
128
+ expect(screen.getByTestId("array-length")).toHaveTextContent("3");
126
129
  });
127
130
  });
128
131
 
129
- it('should NOT re-render when array is mutated in place (requires immutable updates)', async () => {
132
+ it("should NOT re-render when array is mutated in place (requires immutable updates)", async () => {
130
133
  // This tests the case where an array is mutated in place and notifySubscribers is called.
131
- //
134
+ //
132
135
  // IMPORTANT BEHAVIORAL NOTE:
133
- // The OLD implementation (useState + useEffect) had special handling that would
134
- // spread the array when it detected same-reference mutation:
136
+ // The OLD implementation (useState + useEffect) had special handling that would
137
+ // spread the array when it detected same-reference mutation:
135
138
  // if (isArray(newData) && newData === data) setData([...newData])
136
- //
139
+ //
137
140
  // The NEW implementation (useSyncExternalStore) does NOT do this.
138
141
  // useSyncExternalStore uses Object.is() comparison on getSnapshot results.
139
142
  // If the reference is the same, React won't re-render.
@@ -141,132 +144,132 @@ describe('useObservable', () => {
141
144
  // This is actually the CORRECT behavior - it encourages immutable data patterns
142
145
  // which are required for React 18 concurrent rendering to work correctly.
143
146
  // Mutating data in place can cause tearing in concurrent mode.
144
-
145
- const obs = observable(['a', 'b']);
147
+
148
+ const obs = observable(["a", "b"]);
146
149
  render(<ArrayTestComponent obs={obs} />);
147
-
148
- expect(screen.getByTestId('array-length')).toHaveTextContent('2');
149
-
150
+
151
+ expect(screen.getByTestId("array-length")).toHaveTextContent("2");
152
+
150
153
  act(() => {
151
154
  // Mutate the array in place (BAD PRACTICE - don't do this!)
152
155
  const arr = obs();
153
- arr.push('c');
156
+ arr.push("c");
154
157
  obs.notifySubscribers();
155
158
  });
156
-
159
+
157
160
  // The observable itself has the mutated value
158
- expect(obs()).toEqual(['a', 'b', 'c']);
159
-
161
+ expect(obs()).toEqual(["a", "b", "c"]);
162
+
160
163
  // But the component did NOT re-render because the reference didn't change
161
164
  // This is expected with useSyncExternalStore - it requires immutable updates
162
- expect(screen.getByTestId('array-length')).toHaveTextContent('2');
165
+ expect(screen.getByTestId("array-length")).toHaveTextContent("2");
163
166
  });
164
167
 
165
- it('should re-render when array is properly updated with spread', async () => {
168
+ it("should re-render when array is properly updated with spread", async () => {
166
169
  // This is the recommended pattern for updating arrays
167
- const obs = observable(['a', 'b']);
170
+ const obs = observable(["a", "b"]);
168
171
  render(<ArrayTestComponent obs={obs} />);
169
-
170
- expect(screen.getByTestId('array-length')).toHaveTextContent('2');
171
-
172
+
173
+ expect(screen.getByTestId("array-length")).toHaveTextContent("2");
174
+
172
175
  act(() => {
173
- obs([...obs(), 'c']); // Proper immutable update
176
+ obs([...obs(), "c"]); // Proper immutable update
174
177
  });
175
-
178
+
176
179
  await waitFor(() => {
177
- expect(screen.getByTestId('array-length')).toHaveTextContent('3');
178
- expect(screen.getByTestId('array-value')).toHaveTextContent('["a","b","c"]');
180
+ expect(screen.getByTestId("array-length")).toHaveTextContent("3");
181
+ expect(screen.getByTestId("array-value")).toHaveTextContent('["a","b","c"]');
179
182
  });
180
183
  });
181
184
  });
182
185
 
183
- describe('subscription management', () => {
184
- it('should unsubscribe when component unmounts', () => {
185
- const obs = observable('test');
186
+ describe("subscription management", () => {
187
+ it("should unsubscribe when component unmounts", () => {
188
+ const obs = observable("test");
186
189
  const initialSubscriberCount = obs.subscriberCount();
187
-
190
+
188
191
  const { unmount } = render(<TestComponent obs={obs} />);
189
-
192
+
190
193
  // Should have subscribed
191
194
  expect(obs.subscriberCount()).toBeGreaterThan(initialSubscriberCount);
192
-
195
+
193
196
  unmount();
194
-
197
+
195
198
  // Should have unsubscribed
196
199
  expect(obs.subscriberCount()).toBe(initialSubscriberCount);
197
200
  });
198
201
 
199
- it('should handle multiple components subscribing to same observable', async () => {
200
- const obs = observable('shared');
201
-
202
+ it("should handle multiple components subscribing to same observable", async () => {
203
+ const obs = observable("shared");
204
+
202
205
  render(
203
206
  <div>
204
207
  <TestComponent obs={obs} testId="value1" />
205
208
  <TestComponent obs={obs} testId="value2" />
206
- </div>
209
+ </div>,
207
210
  );
208
-
209
- expect(screen.getByTestId('value1')).toHaveTextContent('"shared"');
210
- expect(screen.getByTestId('value2')).toHaveTextContent('"shared"');
211
-
211
+
212
+ expect(screen.getByTestId("value1")).toHaveTextContent('"shared"');
213
+ expect(screen.getByTestId("value2")).toHaveTextContent('"shared"');
214
+
212
215
  act(() => {
213
- obs('updated');
216
+ obs("updated");
214
217
  });
215
-
218
+
216
219
  await waitFor(() => {
217
- expect(screen.getByTestId('value1')).toHaveTextContent('"updated"');
218
- expect(screen.getByTestId('value2')).toHaveTextContent('"updated"');
220
+ expect(screen.getByTestId("value1")).toHaveTextContent('"updated"');
221
+ expect(screen.getByTestId("value2")).toHaveTextContent('"updated"');
219
222
  });
220
223
  });
221
224
  });
222
225
 
223
- describe('rapid updates', () => {
224
- it('should handle rapid sequential updates correctly', async () => {
226
+ describe("rapid updates", () => {
227
+ it("should handle rapid sequential updates correctly", async () => {
225
228
  const obs = observable(0);
226
-
229
+
227
230
  function CounterComponent() {
228
231
  const [value] = useObservable(obs);
229
232
  return <span data-testid="counter">{value}</span>;
230
233
  }
231
-
234
+
232
235
  render(<CounterComponent />);
233
-
236
+
234
237
  act(() => {
235
238
  for (let i = 1; i <= 10; i++) {
236
239
  obs(i);
237
240
  }
238
241
  });
239
-
242
+
240
243
  await waitFor(() => {
241
- expect(screen.getByTestId('counter')).toHaveTextContent('10');
244
+ expect(screen.getByTestId("counter")).toHaveTextContent("10");
242
245
  });
243
246
  });
244
247
  });
245
248
  });
246
249
 
247
- describe('useObservableState', () => {
248
- it('should create an observable with the initial value', () => {
250
+ describe("useObservableState", () => {
251
+ it("should create an observable with the initial value", () => {
249
252
  render(<ObservableStateTestComponent initialValue={42} />);
250
- expect(screen.getByTestId('state-value')).toHaveTextContent('42');
253
+ expect(screen.getByTestId("state-value")).toHaveTextContent("42");
251
254
  });
252
255
 
253
- it('should re-render when the observable is updated', async () => {
256
+ it("should re-render when the observable is updated", async () => {
254
257
  render(<ObservableStateTestComponent initialValue={0} />);
255
-
256
- expect(screen.getByTestId('state-value')).toHaveTextContent('0');
257
-
258
+
259
+ expect(screen.getByTestId("state-value")).toHaveTextContent("0");
260
+
258
261
  act(() => {
259
- screen.getByTestId('increment').click();
262
+ screen.getByTestId("increment").click();
260
263
  });
261
-
264
+
262
265
  await waitFor(() => {
263
- expect(screen.getByTestId('state-value')).toHaveTextContent('1');
266
+ expect(screen.getByTestId("state-value")).toHaveTextContent("1");
264
267
  });
265
268
  });
266
269
 
267
- it('should maintain the same observable instance across re-renders', async () => {
270
+ it("should maintain the same observable instance across re-renders", async () => {
268
271
  let capturedObs: Observable<number> | null = null;
269
-
272
+
270
273
  function CaptureObsComponent() {
271
274
  const obs = useObservableState(0);
272
275
  if (!capturedObs) {
@@ -276,49 +279,53 @@ describe('useObservableState', () => {
276
279
  return (
277
280
  <div>
278
281
  <span data-testid="value">{value}</span>
279
- <span data-testid="same-obs">{obs === capturedObs ? 'same' : 'different'}</span>
280
- <button data-testid="update" onClick={() => obs(obs() + 1)}>Update</button>
282
+ <span data-testid="same-obs">{obs === capturedObs ? "same" : "different"}</span>
283
+ <button type="button" data-testid="update" onClick={() => obs(obs() + 1)}>
284
+ Update
285
+ </button>
281
286
  </div>
282
287
  );
283
288
  }
284
-
289
+
285
290
  render(<CaptureObsComponent />);
286
-
287
- expect(screen.getByTestId('same-obs')).toHaveTextContent('same');
288
-
291
+
292
+ expect(screen.getByTestId("same-obs")).toHaveTextContent("same");
293
+
289
294
  // Trigger a re-render by updating the observable
290
295
  act(() => {
291
- screen.getByTestId('update').click();
296
+ screen.getByTestId("update").click();
292
297
  });
293
-
298
+
294
299
  await waitFor(() => {
295
- expect(screen.getByTestId('value')).toHaveTextContent('1');
296
- expect(screen.getByTestId('same-obs')).toHaveTextContent('same');
300
+ expect(screen.getByTestId("value")).toHaveTextContent("1");
301
+ expect(screen.getByTestId("same-obs")).toHaveTextContent("same");
297
302
  });
298
303
  });
299
304
 
300
- it('should not subscribe when doNotSubscribe is true', () => {
305
+ it("should not subscribe when doNotSubscribe is true", () => {
301
306
  function NoSubscribeComponent() {
302
307
  const obs = useObservableState(0, true); // doNotSubscribe = true
303
308
  return (
304
309
  <div>
305
310
  <span data-testid="value">{obs()}</span>
306
- <button data-testid="update" onClick={() => obs(obs() + 1)}>Update</button>
311
+ <button type="button" data-testid="update" onClick={() => obs(obs() + 1)}>
312
+ Update
313
+ </button>
307
314
  </div>
308
315
  );
309
316
  }
310
-
317
+
311
318
  render(<NoSubscribeComponent />);
312
-
313
- expect(screen.getByTestId('value')).toHaveTextContent('0');
314
-
319
+
320
+ expect(screen.getByTestId("value")).toHaveTextContent("0");
321
+
315
322
  // Update won't cause re-render since we're not subscribed
316
323
  act(() => {
317
- screen.getByTestId('update').click();
324
+ screen.getByTestId("update").click();
318
325
  });
319
-
326
+
320
327
  // Value in DOM should still show 0 since component didn't re-render
321
328
  // But the observable itself has been updated
322
- expect(screen.getByTestId('value')).toHaveTextContent('0');
329
+ expect(screen.getByTestId("value")).toHaveTextContent("0");
323
330
  });
324
331
  });
package/src/hooks.ts CHANGED
@@ -1,8 +1,11 @@
1
-
2
- import { isSubscribable, observable, Observable, unwrapObservable } from "@peers-app/peers-sdk";
3
- import React, { useCallback, useEffect, useState, useSyncExternalStore } from 'react';
4
-
5
-
1
+ import {
2
+ isSubscribable,
3
+ type Observable,
4
+ observable,
5
+ unwrapObservable,
6
+ } from "@peers-app/peers-sdk";
7
+ import type React from "react";
8
+ import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
6
9
 
7
10
  /**
8
11
  * Use this to subscribe to an observable or computed in a functional component.
@@ -11,16 +14,22 @@ import React, { useCallback, useEffect, useState, useSyncExternalStore } from 'r
11
14
  * @param deps an array of dependencies that will cause re-subscription when changed
12
15
  * @returns the current value of the observable or computed and a function to set the value
13
16
  */
14
- export function useObservable<T>(sub: Observable<T> | T, deps: React.DependencyList = []): [T, (value: T) => void] {
17
+ export function useObservable<T>(
18
+ sub: Observable<T> | T,
19
+ deps: React.DependencyList = [],
20
+ ): [T, (value: T) => void] {
15
21
  // Create memoized subscribe function that adapts Observable's subscribe API to useSyncExternalStore's expected signature
16
- const subscribe = useCallback((onStoreChange: () => void) => {
17
- if (!isSubscribable(sub)) {
18
- // If not subscribable, return a no-op unsubscribe
19
- return () => {};
20
- }
21
- const subscription = sub.subscribe(onStoreChange);
22
- return () => subscription.dispose();
23
- }, [sub, ...deps]);
22
+ const subscribe = useCallback(
23
+ (onStoreChange: () => void) => {
24
+ if (!isSubscribable(sub)) {
25
+ // If not subscribable, return a no-op unsubscribe
26
+ return () => {};
27
+ }
28
+ const subscription = sub.subscribe(onStoreChange);
29
+ return () => subscription.dispose();
30
+ },
31
+ [sub, ...deps],
32
+ );
24
33
 
25
34
  // getSnapshot returns the current value from the observable
26
35
  const getSnapshot = useCallback(() => {
@@ -31,11 +40,14 @@ export function useObservable<T>(sub: Observable<T> | T, deps: React.DependencyL
31
40
  const data = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
32
41
 
33
42
  // Create setter that updates both local understanding and the observable
34
- const setter = useCallback((newData: T) => {
35
- if (isSubscribable(sub)) {
36
- sub(newData);
37
- }
38
- }, [sub]);
43
+ const setter = useCallback(
44
+ (newData: T) => {
45
+ if (isSubscribable(sub)) {
46
+ sub(newData);
47
+ }
48
+ },
49
+ [sub],
50
+ );
39
51
 
40
52
  return [data, setter];
41
53
  }
@@ -47,23 +59,28 @@ export function useObservable<T>(sub: Observable<T> | T, deps: React.DependencyL
47
59
  * @param deps the dependencies to pass to useEffect which will trigger a re-render when any of the dependencies change
48
60
  * @returns the initial value and the resolved value of the promise
49
61
  */
50
- export function usePromise<T>(p: Promise<T> | (() => Promise<T>), initialValue?: T, deps: React.DependencyList = []): T | undefined {
62
+ export function usePromise<T>(
63
+ p: Promise<T> | (() => Promise<T>),
64
+ initialValue?: T,
65
+ deps: React.DependencyList = [],
66
+ ): T | undefined {
51
67
  const [data, setData] = useState(initialValue);
68
+ // `deps` drives when the effect re-runs; callers include `p` when it changes. Omitting `p` avoids duplicate tracking with `[...deps]`.
52
69
  useEffect(() => {
53
70
  let disposed = false;
54
- if (typeof p === 'function') {
71
+ if (typeof p === "function") {
55
72
  p = p();
56
73
  }
57
- p.then(newData => {
74
+ p.then((newData) => {
58
75
  // if (!_.isEqual(newData, data) && !disposed) {
59
76
  if (!disposed) {
60
- setData(newData)
77
+ setData(newData);
61
78
  }
62
- })
63
- return () => {
64
- disposed = true;
65
- }
66
- }, deps);
79
+ });
80
+ return () => {
81
+ disposed = true;
82
+ };
83
+ }, [...deps]);
67
84
  return data;
68
85
  }
69
86
 
@@ -76,10 +93,16 @@ export function usePromise<T>(p: Promise<T> | (() => Promise<T>), initialValue?:
76
93
  * @returns
77
94
  */
78
95
  export function useObservableState<T>(initialValue: T, doNotSubscribe?: boolean): Observable<T>;
79
- export function useObservableState<T = undefined>(initialValue?: T, doNotSubscribe?: boolean): Observable<T | undefined>;
96
+ export function useObservableState<T = undefined>(
97
+ initialValue?: T,
98
+ doNotSubscribe?: boolean,
99
+ ): Observable<T | undefined>;
80
100
  export function useObservableState<T>(initialValue?: T, doNotSubscribe?: boolean) {
81
- const [obs] = useState(() => initialValue !== undefined ? observable<T>(initialValue) : observable<T | undefined>());
101
+ const [obs] = useState(() =>
102
+ initialValue !== undefined ? observable<T>(initialValue) : observable<T | undefined>(),
103
+ );
82
104
  if (!doNotSubscribe) {
105
+ // biome-ignore lint/correctness/useHookAtTopLevel: when doNotSubscribe, caller keeps a stable observable without React-driven subscription (see docstring).
83
106
  useObservable(obs);
84
107
  }
85
108
  return obs;
@@ -87,32 +110,41 @@ export function useObservableState<T>(initialValue?: T, doNotSubscribe?: boolean
87
110
 
88
111
  /**
89
112
  * Use this to register a handler for a knockout subscribable in a functional component.
90
- * The handler will be called immediately and whenever the subscribable changes.
113
+ * The handler will be called immediately and whenever the subscribable changes.
91
114
  * @param subscribable The observable to subscribe to
92
115
  * @param onChange The function to call with the new value when the observable changes
93
- */
94
- export function useSubscription<T>(subscribable: Observable<T>, onChange: (value: T) => any, doNotCallOnChangeDuringSetup?: boolean): void {
95
- if (!doNotCallOnChangeDuringSetup) {
96
- useEffect(() => onChange(subscribable()), []);
97
- }
116
+ */
117
+ export function useSubscription<T>(
118
+ subscribable: Observable<T>,
119
+ onChange: (value: T) => void,
120
+ doNotCallOnChangeDuringSetup?: boolean,
121
+ ): void {
98
122
  useEffect(() => {
123
+ if (!doNotCallOnChangeDuringSetup) {
124
+ onChange(subscribable());
125
+ }
99
126
  const subscription = subscribable.subscribe(() => onChange(subscribable()));
100
127
  return () => subscription.dispose();
101
- }, [subscribable, onChange]);
128
+ }, [subscribable, onChange, doNotCallOnChangeDuringSetup]);
102
129
  }
103
130
 
104
- export function useOnScreen(ref: React.RefObject<any>) {
105
- const [isIntersecting, setIntersecting] = useState(false)
106
-
107
- const observer = new IntersectionObserver(
108
- ([entry]) => setIntersecting(entry.isIntersecting)
109
- )
131
+ export function useOnScreen(ref: React.RefObject<Element | null>) {
132
+ const [isIntersecting, setIntersecting] = useState(false);
110
133
 
134
+ // Read ref.current once on mount (ref object identity is stable).
111
135
  useEffect(() => {
112
- observer.observe(ref.current)
113
- // Remove the observer as soon as the component is unmounted
114
- return () => { observer.disconnect() }
115
- }, [])
136
+ const element = ref.current;
137
+ if (!element) {
138
+ return;
139
+ }
140
+ const observer = new IntersectionObserver(([entry]) => {
141
+ setIntersecting(entry.isIntersecting);
142
+ });
143
+ observer.observe(element);
144
+ return () => {
145
+ observer.disconnect();
146
+ };
147
+ }, []);
116
148
 
117
- return isIntersecting
149
+ return isIntersecting;
118
150
  }