@dxos/plugin-thread 0.8.4-main.c1de068 → 0.8.4-main.dedc0f3

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 (249) hide show
  1. package/dist/lib/browser/ChannelContainer-4PYFOIDK.mjs +16 -0
  2. package/dist/lib/browser/{ChatContainer-AIGIWAUG.mjs → ChatContainer-EXNNREYB.mjs} +4 -4
  3. package/dist/lib/browser/{app-graph-builder-N7BE5TYW.mjs → app-graph-builder-FHAOPO57.mjs} +5 -5
  4. package/dist/lib/browser/app-graph-builder-FHAOPO57.mjs.map +7 -0
  5. package/dist/lib/browser/{call-manager-NIPDYUVD.mjs → call-manager-RMPTWSTF.mjs} +4 -4
  6. package/dist/lib/browser/call-manager-RMPTWSTF.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-LGSDKXPF.mjs → chunk-4APCDRSH.mjs} +1 -1
  8. package/dist/lib/browser/{chunk-LGSDKXPF.mjs.map → chunk-4APCDRSH.mjs.map} +1 -1
  9. package/dist/lib/browser/{chunk-KWPRNHTK.mjs → chunk-7W7QK5ZP.mjs} +232 -230
  10. package/dist/lib/browser/chunk-7W7QK5ZP.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-G4CNILRR.mjs +23 -0
  12. package/dist/lib/browser/{chunk-4OYAYON2.mjs.map → chunk-G4CNILRR.mjs.map} +3 -3
  13. package/dist/lib/browser/{chunk-GCLD5ESP.mjs → chunk-KTWYVC5X.mjs} +22 -12
  14. package/dist/lib/browser/chunk-KTWYVC5X.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-PBMNFG3B.mjs +376 -0
  16. package/dist/lib/browser/chunk-PBMNFG3B.mjs.map +7 -0
  17. package/dist/lib/browser/{chunk-ERWASUQK.mjs → chunk-RI3Z6MK3.mjs} +38 -6
  18. package/dist/lib/{node-esm/chunk-25ABSYP2.mjs.map → browser/chunk-RI3Z6MK3.mjs.map} +4 -4
  19. package/dist/lib/browser/{chunk-6NSMBO3B.mjs → chunk-XYUJXHYV.mjs} +55 -5
  20. package/dist/lib/browser/chunk-XYUJXHYV.mjs.map +7 -0
  21. package/dist/lib/browser/{chunk-ZE7KWN36.mjs → chunk-ZCEU5K23.mjs} +461 -240
  22. package/dist/lib/browser/chunk-ZCEU5K23.mjs.map +7 -0
  23. package/dist/lib/browser/chunk-ZH2WPTNP.mjs +618 -0
  24. package/dist/lib/browser/chunk-ZH2WPTNP.mjs.map +7 -0
  25. package/dist/lib/browser/index.mjs +20 -9
  26. package/dist/lib/browser/index.mjs.map +3 -3
  27. package/dist/lib/browser/{intent-resolver-JFGLZ6VI.mjs → intent-resolver-J7OCP2P6.mjs} +67 -9
  28. package/dist/lib/browser/intent-resolver-J7OCP2P6.mjs.map +7 -0
  29. package/dist/lib/browser/{markdown-GEXBVJYN.mjs → markdown-Z677YORA.mjs} +4 -4
  30. package/dist/lib/browser/markdown-Z677YORA.mjs.map +7 -0
  31. package/dist/lib/browser/meta.json +1 -1
  32. package/dist/lib/browser/{react-root-X4UIVGTX.mjs → react-root-XKOD4KRB.mjs} +9 -9
  33. package/dist/lib/browser/{react-surface-YCYEFBPM.mjs → react-surface-GVGZRBDK.mjs} +9 -9
  34. package/dist/lib/browser/react-surface-GVGZRBDK.mjs.map +7 -0
  35. package/dist/lib/browser/{settings-E32MTL7A.mjs → settings-GRKEID73.mjs} +2 -2
  36. package/dist/lib/browser/{state-H6CYONWE.mjs → state-2T4KDWBX.mjs} +2 -2
  37. package/dist/lib/browser/{state-H6CYONWE.mjs.map → state-2T4KDWBX.mjs.map} +2 -2
  38. package/dist/lib/browser/toolkit-KUPIIFC6.mjs +80 -0
  39. package/dist/lib/browser/toolkit-KUPIIFC6.mjs.map +7 -0
  40. package/dist/lib/browser/types/index.mjs +1 -1
  41. package/dist/lib/node-esm/{ChannelContainer-DET6T5N2.mjs → ChannelContainer-KQQEZWUN.mjs} +7 -7
  42. package/dist/lib/node-esm/{ChatContainer-IQUWU426.mjs → ChatContainer-QVQKFBWA.mjs} +4 -4
  43. package/dist/lib/node-esm/{app-graph-builder-VCW76Z6V.mjs → app-graph-builder-XEYWX5FC.mjs} +5 -5
  44. package/dist/lib/node-esm/app-graph-builder-XEYWX5FC.mjs.map +7 -0
  45. package/dist/lib/node-esm/{call-manager-D6O5ZJJ4.mjs → call-manager-QAREJFVX.mjs} +4 -4
  46. package/dist/lib/node-esm/call-manager-QAREJFVX.mjs.map +7 -0
  47. package/dist/lib/node-esm/{chunk-OU5BYIDO.mjs → chunk-2V3WSLUZ.mjs} +55 -5
  48. package/dist/lib/node-esm/chunk-2V3WSLUZ.mjs.map +7 -0
  49. package/dist/lib/node-esm/chunk-4T45PCKP.mjs +25 -0
  50. package/dist/lib/node-esm/{chunk-GW37AAM4.mjs.map → chunk-4T45PCKP.mjs.map} +3 -3
  51. package/dist/lib/node-esm/chunk-DKCQCENK.mjs +377 -0
  52. package/dist/lib/node-esm/chunk-DKCQCENK.mjs.map +7 -0
  53. package/dist/lib/node-esm/{chunk-25ABSYP2.mjs → chunk-JNPKMT7O.mjs} +38 -6
  54. package/dist/lib/node-esm/chunk-JNPKMT7O.mjs.map +7 -0
  55. package/dist/lib/node-esm/chunk-LE2LIYGX.mjs +619 -0
  56. package/dist/lib/node-esm/chunk-LE2LIYGX.mjs.map +7 -0
  57. package/dist/lib/node-esm/{chunk-TDFHTCZZ.mjs → chunk-P6E247RF.mjs} +22 -12
  58. package/dist/lib/node-esm/chunk-P6E247RF.mjs.map +7 -0
  59. package/dist/lib/node-esm/{chunk-75RJ5DJI.mjs → chunk-PYTVBN5J.mjs} +1 -1
  60. package/dist/lib/node-esm/{chunk-75RJ5DJI.mjs.map → chunk-PYTVBN5J.mjs.map} +1 -1
  61. package/dist/lib/node-esm/{chunk-C3XFMUAY.mjs → chunk-SNKFZQST.mjs} +232 -230
  62. package/dist/lib/node-esm/chunk-SNKFZQST.mjs.map +7 -0
  63. package/dist/lib/node-esm/{chunk-IHDNQLLR.mjs → chunk-TIPIOH2J.mjs} +461 -240
  64. package/dist/lib/node-esm/chunk-TIPIOH2J.mjs.map +7 -0
  65. package/dist/lib/node-esm/index.mjs +20 -9
  66. package/dist/lib/node-esm/index.mjs.map +3 -3
  67. package/dist/lib/node-esm/{intent-resolver-OXGNQ44Y.mjs → intent-resolver-SR2LW4HF.mjs} +67 -9
  68. package/dist/lib/node-esm/intent-resolver-SR2LW4HF.mjs.map +7 -0
  69. package/dist/lib/node-esm/{markdown-LIJZDK2J.mjs → markdown-FL4IXLRP.mjs} +4 -4
  70. package/dist/lib/node-esm/markdown-FL4IXLRP.mjs.map +7 -0
  71. package/dist/lib/node-esm/meta.json +1 -1
  72. package/dist/lib/node-esm/{react-root-M7J6PZVW.mjs → react-root-4JWDRIQB.mjs} +9 -9
  73. package/dist/lib/node-esm/{react-surface-IRSEKM42.mjs → react-surface-WLQUVRAX.mjs} +9 -9
  74. package/dist/lib/node-esm/react-surface-WLQUVRAX.mjs.map +7 -0
  75. package/dist/lib/node-esm/{settings-SJ24YG7M.mjs → settings-F2ODVNNW.mjs} +2 -2
  76. package/dist/lib/node-esm/{state-D7WNDT3N.mjs → state-62TQKQVB.mjs} +2 -2
  77. package/dist/lib/node-esm/{state-D7WNDT3N.mjs.map → state-62TQKQVB.mjs.map} +2 -2
  78. package/dist/lib/node-esm/toolkit-KZIS7G36.mjs +81 -0
  79. package/dist/lib/node-esm/toolkit-KZIS7G36.mjs.map +7 -0
  80. package/dist/lib/node-esm/types/index.mjs +1 -1
  81. package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
  82. package/dist/types/src/calls/call-manager.d.ts.map +1 -1
  83. package/dist/types/src/calls/call-swarm-synchronizer.d.ts.map +1 -1
  84. package/dist/types/src/calls/media-manager.d.ts +1 -1
  85. package/dist/types/src/calls/media-manager.d.ts.map +1 -1
  86. package/dist/types/src/calls/types.d.ts +1 -1
  87. package/dist/types/src/calls/types.d.ts.map +1 -1
  88. package/dist/types/src/calls/util/calls-service.d.ts +4 -2
  89. package/dist/types/src/calls/util/calls-service.d.ts.map +1 -1
  90. package/dist/types/src/calls/util/stub-media-streams.d.ts.map +1 -1
  91. package/dist/types/src/calls-service.stories.d.ts +10 -8
  92. package/dist/types/src/calls-service.stories.d.ts.map +1 -1
  93. package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -1
  94. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  95. package/dist/types/src/capabilities/call-manager.d.ts +1 -1
  96. package/dist/types/src/capabilities/call-manager.d.ts.map +1 -1
  97. package/dist/types/src/capabilities/capabilities.d.ts +2 -2
  98. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  99. package/dist/types/src/capabilities/index.d.ts +11 -10
  100. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  101. package/dist/types/src/capabilities/intent-resolver.d.ts +1 -1
  102. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  103. package/dist/types/src/capabilities/markdown.d.ts +1 -1
  104. package/dist/types/src/capabilities/markdown.d.ts.map +1 -1
  105. package/dist/types/src/capabilities/react-root.d.ts +2 -2
  106. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  107. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  108. package/dist/types/src/capabilities/settings.d.ts +1 -1
  109. package/dist/types/src/capabilities/state.d.ts +2 -2
  110. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  111. package/dist/types/src/capabilities/toolkit.d.ts +5 -0
  112. package/dist/types/src/capabilities/toolkit.d.ts.map +1 -0
  113. package/dist/types/src/components/Call/Call.d.ts.map +1 -1
  114. package/dist/types/src/components/Call/Toolbar.d.ts.map +1 -1
  115. package/dist/types/src/components/CallDebugPanel.d.ts +7 -0
  116. package/dist/types/src/components/CallDebugPanel.d.ts.map +1 -1
  117. package/dist/types/src/components/CallSidebar.d.ts.map +1 -1
  118. package/dist/types/src/components/ChannelContainer.d.ts.map +1 -1
  119. package/dist/types/src/components/ChannelContainer.stories.d.ts +88 -3
  120. package/dist/types/src/components/ChannelContainer.stories.d.ts.map +1 -1
  121. package/dist/types/src/components/Chat.stories.d.ts +90 -4
  122. package/dist/types/src/components/Chat.stories.d.ts.map +1 -1
  123. package/dist/types/src/components/ChatContainer.d.ts.map +1 -1
  124. package/dist/types/src/components/Comments.stories.d.ts +156 -3
  125. package/dist/types/src/components/Comments.stories.d.ts.map +1 -1
  126. package/dist/types/src/components/CommentsContainer.d.ts.map +1 -1
  127. package/dist/types/src/components/CommentsThreadContainer.d.ts +2 -2
  128. package/dist/types/src/components/CommentsThreadContainer.d.ts.map +1 -1
  129. package/dist/types/src/components/Lobby/Lobby.d.ts.map +1 -1
  130. package/dist/types/src/components/Media/VideoObject.d.ts +1 -1
  131. package/dist/types/src/components/Media/VideoObject.stories.d.ts +15 -3
  132. package/dist/types/src/components/Media/VideoObject.stories.d.ts.map +1 -1
  133. package/dist/types/src/components/MessageContainer.d.ts +4 -1
  134. package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
  135. package/dist/types/src/components/Participant/Participant.d.ts.map +1 -1
  136. package/dist/types/src/components/Participant/ParticipantGrid.d.ts.map +1 -1
  137. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.stories.d.ts +154 -2
  138. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.stories.d.ts.map +1 -1
  139. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.stories.d.ts +156 -3
  140. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.stories.d.ts.map +1 -1
  141. package/dist/types/src/components/ResponsiveGrid/ResponsiveGridItem.d.ts.map +1 -1
  142. package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -1
  143. package/dist/types/src/components/command-extension.d.ts.map +1 -1
  144. package/dist/types/src/components/testing.d.ts +1 -0
  145. package/dist/types/src/components/testing.d.ts.map +1 -1
  146. package/dist/types/src/extensions/threads.d.ts +2 -2
  147. package/dist/types/src/extensions/threads.d.ts.map +1 -1
  148. package/dist/types/src/hooks/index.d.ts +1 -0
  149. package/dist/types/src/hooks/index.d.ts.map +1 -1
  150. package/dist/types/src/hooks/useBlackCanvasStreamTrack.d.ts +2 -0
  151. package/dist/types/src/hooks/useBlackCanvasStreamTrack.d.ts.map +1 -0
  152. package/dist/types/src/hooks/useVideoStreamTrack.d.ts.map +1 -1
  153. package/dist/types/src/translations.d.ts +3 -1
  154. package/dist/types/src/translations.d.ts.map +1 -1
  155. package/dist/types/src/types/schema.d.ts +123 -108
  156. package/dist/types/src/types/schema.d.ts.map +1 -1
  157. package/dist/types/src/types/types.d.ts +255 -170
  158. package/dist/types/src/types/types.d.ts.map +1 -1
  159. package/dist/types/tsconfig.tsbuildinfo +1 -1
  160. package/package.json +54 -50
  161. package/src/ThreadPlugin.tsx +9 -2
  162. package/src/calls/call-manager.ts +2 -2
  163. package/src/calls/call-swarm-synchronizer.ts +1 -1
  164. package/src/calls/media-manager.ts +60 -34
  165. package/src/calls/types.ts +8 -4
  166. package/src/calls/util/calls-service.ts +65 -14
  167. package/src/calls/util/stub-media-streams.ts +62 -5
  168. package/src/calls-service.stories.tsx +76 -60
  169. package/src/capabilities/app-graph-builder.ts +8 -5
  170. package/src/capabilities/call-manager.ts +3 -2
  171. package/src/capabilities/capabilities.ts +2 -2
  172. package/src/capabilities/index.ts +1 -0
  173. package/src/capabilities/intent-resolver.ts +38 -4
  174. package/src/capabilities/markdown.ts +4 -3
  175. package/src/capabilities/react-surface.tsx +5 -4
  176. package/src/capabilities/state.ts +2 -1
  177. package/src/capabilities/toolkit.ts +78 -0
  178. package/src/components/Call/Call.tsx +3 -2
  179. package/src/components/Call/Toolbar.tsx +5 -5
  180. package/src/components/CallDebugPanel.tsx +108 -49
  181. package/src/components/CallSidebar.tsx +2 -1
  182. package/src/components/ChannelContainer.stories.tsx +6 -5
  183. package/src/components/ChannelContainer.tsx +7 -6
  184. package/src/components/Chat.stories.tsx +28 -26
  185. package/src/components/ChatContainer.tsx +8 -4
  186. package/src/components/Comments.stories.tsx +33 -29
  187. package/src/components/CommentsContainer.tsx +2 -1
  188. package/src/components/CommentsThreadContainer.tsx +9 -7
  189. package/src/components/Lobby/Lobby.tsx +1 -1
  190. package/src/components/Media/VideoObject.stories.tsx +30 -27
  191. package/src/components/MessageContainer.tsx +43 -9
  192. package/src/components/Participant/Participant.tsx +13 -7
  193. package/src/components/Participant/ParticipantGrid.tsx +2 -1
  194. package/src/components/ResponsiveGrid/ResponsiveContainer.stories.tsx +6 -5
  195. package/src/components/ResponsiveGrid/ResponsiveGrid.stories.tsx +42 -39
  196. package/src/components/ResponsiveGrid/ResponsiveGrid.tsx +1 -1
  197. package/src/components/ResponsiveGrid/ResponsiveGridItem.tsx +2 -2
  198. package/src/components/ThreadComplementary.tsx +38 -8
  199. package/src/components/ThreadSettings.tsx +2 -2
  200. package/src/components/command-extension.ts +5 -2
  201. package/src/components/testing.ts +22 -0
  202. package/src/extensions/threads.ts +6 -6
  203. package/src/hooks/index.ts +1 -0
  204. package/src/hooks/useBlackCanvasStreamTrack.tsx +27 -0
  205. package/src/hooks/useDebugMode.ts +1 -1
  206. package/src/hooks/useVideoStreamTrack.tsx +5 -2
  207. package/src/testing/stories.ts +2 -2
  208. package/src/translations.ts +3 -1
  209. package/src/types/types.ts +21 -12
  210. package/src/typings/peermetrics-webrtc-stats.d.ts +17 -1
  211. package/src/typings.d.ts +8 -0
  212. package/dist/lib/browser/ChannelContainer-IY2UIRLF.mjs +0 -16
  213. package/dist/lib/browser/app-graph-builder-N7BE5TYW.mjs.map +0 -7
  214. package/dist/lib/browser/call-manager-NIPDYUVD.mjs.map +0 -7
  215. package/dist/lib/browser/chunk-4OYAYON2.mjs +0 -21
  216. package/dist/lib/browser/chunk-6NSMBO3B.mjs.map +0 -7
  217. package/dist/lib/browser/chunk-ERWASUQK.mjs.map +0 -7
  218. package/dist/lib/browser/chunk-GCLD5ESP.mjs.map +0 -7
  219. package/dist/lib/browser/chunk-KWPRNHTK.mjs.map +0 -7
  220. package/dist/lib/browser/chunk-VUWC4IJM.mjs +0 -479
  221. package/dist/lib/browser/chunk-VUWC4IJM.mjs.map +0 -7
  222. package/dist/lib/browser/chunk-XLPJXUKO.mjs +0 -367
  223. package/dist/lib/browser/chunk-XLPJXUKO.mjs.map +0 -7
  224. package/dist/lib/browser/chunk-ZE7KWN36.mjs.map +0 -7
  225. package/dist/lib/browser/intent-resolver-JFGLZ6VI.mjs.map +0 -7
  226. package/dist/lib/browser/markdown-GEXBVJYN.mjs.map +0 -7
  227. package/dist/lib/browser/react-surface-YCYEFBPM.mjs.map +0 -7
  228. package/dist/lib/node-esm/app-graph-builder-VCW76Z6V.mjs.map +0 -7
  229. package/dist/lib/node-esm/call-manager-D6O5ZJJ4.mjs.map +0 -7
  230. package/dist/lib/node-esm/chunk-2MFNK53T.mjs +0 -480
  231. package/dist/lib/node-esm/chunk-2MFNK53T.mjs.map +0 -7
  232. package/dist/lib/node-esm/chunk-C3XFMUAY.mjs.map +0 -7
  233. package/dist/lib/node-esm/chunk-GW37AAM4.mjs +0 -23
  234. package/dist/lib/node-esm/chunk-IHDNQLLR.mjs.map +0 -7
  235. package/dist/lib/node-esm/chunk-J4NSON5Q.mjs +0 -368
  236. package/dist/lib/node-esm/chunk-J4NSON5Q.mjs.map +0 -7
  237. package/dist/lib/node-esm/chunk-OU5BYIDO.mjs.map +0 -7
  238. package/dist/lib/node-esm/chunk-TDFHTCZZ.mjs.map +0 -7
  239. package/dist/lib/node-esm/intent-resolver-OXGNQ44Y.mjs.map +0 -7
  240. package/dist/lib/node-esm/markdown-LIJZDK2J.mjs.map +0 -7
  241. package/dist/lib/node-esm/react-surface-IRSEKM42.mjs.map +0 -7
  242. /package/dist/lib/browser/{ChannelContainer-IY2UIRLF.mjs.map → ChannelContainer-4PYFOIDK.mjs.map} +0 -0
  243. /package/dist/lib/browser/{ChatContainer-AIGIWAUG.mjs.map → ChatContainer-EXNNREYB.mjs.map} +0 -0
  244. /package/dist/lib/browser/{react-root-X4UIVGTX.mjs.map → react-root-XKOD4KRB.mjs.map} +0 -0
  245. /package/dist/lib/browser/{settings-E32MTL7A.mjs.map → settings-GRKEID73.mjs.map} +0 -0
  246. /package/dist/lib/node-esm/{ChannelContainer-DET6T5N2.mjs.map → ChannelContainer-KQQEZWUN.mjs.map} +0 -0
  247. /package/dist/lib/node-esm/{ChatContainer-IQUWU426.mjs.map → ChatContainer-QVQKFBWA.mjs.map} +0 -0
  248. /package/dist/lib/node-esm/{react-root-M7J6PZVW.mjs.map → react-root-4JWDRIQB.mjs.map} +0 -0
  249. /package/dist/lib/node-esm/{settings-SJ24YG7M.mjs.map → settings-F2ODVNNW.mjs.map} +0 -0
@@ -4,44 +4,47 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
- import { type StoryObj, type Meta } from '@storybook/react-vite';
7
+ import { type StoryObj } from '@storybook/react-vite';
8
8
  import React, { useEffect, useState } from 'react';
9
9
 
10
10
  import { scheduleTask } from '@dxos/async';
11
11
  import { Context } from '@dxos/context';
12
12
  import { log } from '@dxos/log';
13
- import { withTheme, withLayout } from '@dxos/storybook-utils';
13
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
14
14
 
15
- import { VideoObject } from './VideoObject';
16
15
  import { getUserMediaTrack } from '../../calls';
17
16
  import { ResponsiveContainer } from '../ResponsiveGrid';
18
17
 
19
- const meta: Meta<typeof VideoObject> = {
18
+ import { VideoObject, type VideoObjectProps } from './VideoObject';
19
+
20
+ const DefaultStory = (props: VideoObjectProps) => {
21
+ log.info('render');
22
+ const [videoStream, setVideoStream] = useState<MediaStream>();
23
+ useEffect(() => {
24
+ const ctx = new Context();
25
+ scheduleTask(ctx, async () => {
26
+ const stream = new MediaStream();
27
+ stream.addTrack(await getUserMediaTrack('videoinput'));
28
+ setVideoStream(stream);
29
+ });
30
+
31
+ return () => {
32
+ void ctx.dispose();
33
+ videoStream?.getTracks().forEach((track) => track.stop());
34
+ };
35
+ }, []);
36
+
37
+ return (
38
+ <ResponsiveContainer>
39
+ <VideoObject videoStream={videoStream} flip {...props} />
40
+ </ResponsiveContainer>
41
+ );
42
+ };
43
+
44
+ const meta = {
20
45
  title: 'plugins/plugin-thread/VideoObject',
21
46
  component: VideoObject,
22
- render: (args) => {
23
- log.info('render');
24
- const [videoStream, setVideoStream] = useState<MediaStream>();
25
- useEffect(() => {
26
- const ctx = new Context();
27
- scheduleTask(ctx, async () => {
28
- const stream = new MediaStream();
29
- stream.addTrack(await getUserMediaTrack('videoinput'));
30
- setVideoStream(stream);
31
- });
32
-
33
- return () => {
34
- void ctx.dispose();
35
- videoStream?.getTracks().forEach((track) => track.stop());
36
- };
37
- }, []);
38
-
39
- return (
40
- <ResponsiveContainer>
41
- <VideoObject videoStream={videoStream} {...args} flip />
42
- </ResponsiveContainer>
43
- );
44
- },
47
+ render: DefaultStory,
45
48
  decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'justify-center' })],
46
49
  };
47
50
 
@@ -9,27 +9,37 @@ import { Surface } from '@dxos/app-framework';
9
9
  import { type Obj, Ref, type Type } from '@dxos/echo';
10
10
  import { PublicKey } from '@dxos/react-client';
11
11
  import { type SpaceMember } from '@dxos/react-client/echo';
12
- import { useIdentity, type Identity } from '@dxos/react-client/halo';
12
+ import { type Identity, useIdentity } from '@dxos/react-client/halo';
13
13
  import { IconButton, useOnTransition, useThemeContext, useTranslation } from '@dxos/react-ui';
14
14
  import { createBasicExtensions, createThemeExtensions, useTextEditor } from '@dxos/react-ui-editor';
15
15
  import { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/react-ui-theme';
16
16
  import { MessageHeading, MessageRoot } from '@dxos/react-ui-thread';
17
17
  import { type DataType } from '@dxos/schema';
18
18
 
19
- import { commentControlClassNames } from './CommentsThreadContainer';
20
- import { command } from './command-extension';
21
19
  import { useOnEditAnalytics } from '../hooks';
22
20
  import { meta } from '../meta';
23
21
  import { getMessageMetadata } from '../util';
24
22
 
23
+ import { command } from './command-extension';
24
+
25
+ export const buttonGroupClassNames = 'flex flex-row items-center gap-0.5 pie-2';
26
+ export const buttonClassNames = '!p-1 transition-opacity';
27
+
25
28
  export type MessageContainerProps = {
26
29
  message: DataType.Message;
27
30
  members: SpaceMember[];
28
31
  editable?: boolean;
29
32
  onDelete?: (id: string) => void;
33
+ onAcceptProposal?: (id: string) => void;
30
34
  };
31
35
 
32
- export const MessageContainer = ({ message, members, editable = false, onDelete }: MessageContainerProps) => {
36
+ export const MessageContainer = ({
37
+ message,
38
+ members,
39
+ editable = false,
40
+ onDelete,
41
+ onAcceptProposal,
42
+ }: MessageContainerProps) => {
33
43
  const { t } = useTranslation(meta.id);
34
44
  const senderIdentity = members.find(
35
45
  (member) =>
@@ -39,8 +49,11 @@ export const MessageContainer = ({ message, members, editable = false, onDelete
39
49
  const messageMetadata = getMessageMetadata(message.id, senderIdentity);
40
50
  const userIsAuthor = useIdentity()?.did === messageMetadata.authorId;
41
51
  const [editing, setEditing] = useState(false);
52
+ const handleEdit = useCallback(() => setEditing((editing) => !editing), []);
42
53
  const handleDelete = useCallback(() => onDelete?.(message.id), [message, onDelete]);
54
+ const handleAcceptProposal = useCallback(() => onAcceptProposal?.(message.id), [message, onAcceptProposal]);
43
55
  const textBlock = message.blocks.find((block) => block._tag === 'text');
56
+ const proposalBlock = message.blocks.find((block) => block._tag === 'proposal');
44
57
  const references = message.blocks.filter((block) => block._tag === 'reference').map((block) => block.reference);
45
58
 
46
59
  useOnEditAnalytics(message, textBlock, !!editing);
@@ -48,7 +61,7 @@ export const MessageContainer = ({ message, members, editable = false, onDelete
48
61
  return (
49
62
  <MessageRoot {...messageMetadata} classNames={[hoverableControls, hoverableFocusedWithinControls]}>
50
63
  <MessageHeading authorName={messageMetadata.authorName} timestamp={messageMetadata.timestamp}>
51
- <div className='flex flex-row items-center gap-0.5'>
64
+ <div role='none' className={buttonGroupClassNames}>
52
65
  {userIsAuthor && editable && (
53
66
  <IconButton
54
67
  data-testid={editing ? 'thread.message.save' : 'thread.message.edit'}
@@ -56,8 +69,20 @@ export const MessageContainer = ({ message, members, editable = false, onDelete
56
69
  icon={editing ? 'ph--check--regular' : 'ph--pencil-simple--regular'}
57
70
  iconOnly
58
71
  label={t(editing ? 'save message label' : 'edit message label')}
59
- classNames={[commentControlClassNames, hoverableControlItem]}
60
- onClick={() => setEditing((editing) => !editing)}
72
+ classNames={[buttonClassNames, hoverableControlItem]}
73
+ onClick={handleEdit}
74
+ />
75
+ )}
76
+ {/* TODO(wittjosiah): Proposal controls should probably be hoisted to thread level. */}
77
+ {proposalBlock && onAcceptProposal && (
78
+ <IconButton
79
+ data-testid='thread.message.accept'
80
+ variant='ghost'
81
+ icon='ph--check--regular'
82
+ iconOnly
83
+ label={t('accept proposal label')}
84
+ classNames={[buttonClassNames, hoverableControlItem]}
85
+ onClick={handleAcceptProposal}
61
86
  />
62
87
  )}
63
88
  {onDelete && (
@@ -67,13 +92,14 @@ export const MessageContainer = ({ message, members, editable = false, onDelete
67
92
  icon='ph--x--regular'
68
93
  iconOnly
69
94
  label={t('delete message label')}
70
- classNames={[commentControlClassNames, hoverableControlItem]}
71
- onClick={() => handleDelete()}
95
+ classNames={[buttonClassNames, hoverableControlItem]}
96
+ onClick={handleDelete}
72
97
  />
73
98
  )}
74
99
  </div>
75
100
  </MessageHeading>
76
101
  {textBlock && <TextboxBlock block={textBlock} isAuthor={userIsAuthor} editing={editing} />}
102
+ {proposalBlock && <ProposalBlock block={proposalBlock} />}
77
103
  {Ref.Array.targets(references).map((reference, index) => (
78
104
  <MessagePart key={index} part={reference} />
79
105
  ))}
@@ -132,6 +158,14 @@ const TextboxBlock = ({
132
158
  return <div role='none' ref={parentRef} className='mie-4' {...focusAttributes} />;
133
159
  };
134
160
 
161
+ const ProposalBlock = ({ block }: { block: DataType.MessageBlock.Proposal }) => {
162
+ return (
163
+ <div role='none' className='mie-4 italic'>
164
+ {block.text}
165
+ </div>
166
+ );
167
+ };
168
+
135
169
  const MessageBlockObjectTile = forwardRef<HTMLDivElement, { subject: Obj.Any }>(({ subject }, forwardedRef) => {
136
170
  // TODO(burdon): Use annotation to get title.
137
171
  let title = (subject as any).name ?? (subject as any).title ?? (subject as any).type ?? 'Object';
@@ -17,15 +17,21 @@ export const Participant = memo(({ item: user, debug, ...props }: ResponsiveGrid
17
17
  const call = useCapability(ThreadCapabilities.CallManager);
18
18
  const isSelf: boolean = call.self.id !== undefined && user.id !== undefined && user.id.startsWith(call.self.id);
19
19
  const isScreenshare = user.id?.endsWith(SCREENSHARE_SUFFIX);
20
- const pulledVideoStream = call.getVideoStream(
21
- isScreenshare || !isSelf ? (user.tracks?.video as EncodedTrackName) : undefined,
22
- );
20
+
21
+ const pulledVideoStream =
22
+ !isSelf && isScreenshare && user.tracks?.screenshareEnabled
23
+ ? call.getVideoStream(user.tracks?.screenshare as EncodedTrackName)
24
+ : !isSelf && !isScreenshare && user.tracks?.videoEnabled
25
+ ? call.getVideoStream(user.tracks?.video as EncodedTrackName)
26
+ : undefined;
23
27
 
24
28
  const videoStream = useMemo<MediaStream | undefined>(() => {
25
- if (isSelf && !isScreenshare) {
26
- return call.media.videoStream;
27
- } else if (isSelf && isScreenshare) {
28
- return call.media.screenshareVideoStream;
29
+ if (isSelf) {
30
+ if (isScreenshare) {
31
+ return call.media.screenshareVideoStream;
32
+ } else if (!isScreenshare) {
33
+ return call.media.videoStream;
34
+ }
29
35
  } else if (!isSelf) {
30
36
  return pulledVideoStream;
31
37
  }
@@ -6,10 +6,11 @@ import React, { useEffect, useMemo, useState } from 'react';
6
6
 
7
7
  import { type ThemedClassName } from '@dxos/react-ui';
8
8
 
9
- import { Participant, SCREENSHARE_SUFFIX } from './Participant';
10
9
  import { type UserState } from '../../calls';
11
10
  import { ResponsiveGrid } from '../ResponsiveGrid';
12
11
 
12
+ import { Participant, SCREENSHARE_SUFFIX } from './Participant';
13
+
13
14
  const getId = (user: UserState): string => user.id!;
14
15
 
15
16
  export type ParticipantGridProps = ThemedClassName<{
@@ -4,23 +4,24 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
- import { type StoryObj, type Meta } from '@storybook/react-vite';
7
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
8
  import React from 'react';
9
9
 
10
- import { withTheme, withLayout } from '@dxos/storybook-utils';
10
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
11
11
 
12
- import { ResponsiveContainer } from './ResponsiveContainer';
13
12
  import { translations } from '../../translations';
14
13
  import { VideoObject as VideoObjectComponent } from '../Media';
15
14
 
16
- const meta: Meta<typeof ResponsiveContainer> = {
15
+ import { ResponsiveContainer } from './ResponsiveContainer';
16
+
17
+ const meta = {
17
18
  title: 'plugins/plugin-thread/ResponsiveContainer',
18
19
  component: ResponsiveContainer,
19
20
  decorators: [withTheme, withLayout({ fullscreen: true })],
20
21
  parameters: {
21
22
  translations,
22
23
  },
23
- };
24
+ } satisfies Meta<typeof ResponsiveContainer>;
24
25
 
25
26
  export default meta;
26
27
 
@@ -4,15 +4,16 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
- import { type StoryObj, type Meta } from '@storybook/react-vite';
7
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
8
  import React, { useEffect, useState } from 'react';
9
9
 
10
10
  import { faker } from '@dxos/random';
11
- import { withTheme, withLayout } from '@dxos/storybook-utils';
11
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
12
+
13
+ import { translations } from '../../translations';
12
14
 
13
15
  import { ResponsiveGrid, type ResponsiveGridProps } from './ResponsiveGrid';
14
16
  import { ResponsiveGridItem, type ResponsiveGridItemProps } from './ResponsiveGridItem';
15
- import { translations } from '../../translations';
16
17
 
17
18
  type TestItem = {
18
19
  id: string;
@@ -56,46 +57,48 @@ const TestCell = ({ item, ...props }: ResponsiveGridItemProps<TestItem>) => {
56
57
 
57
58
  type StoryProps = ResponsiveGridProps<TestItem> & { random?: boolean; autoHideGallery?: boolean };
58
59
 
59
- const meta: Meta<StoryProps> = {
60
+ const DefaultStory = (props: StoryProps) => {
61
+ const [pinned, setPinned] = useState<string | undefined>(
62
+ (props.pinned ?? props.items.length > 1) ? props.items[0]?.id : undefined,
63
+ );
64
+ const [items, setItems] = useState<TestItem[]>(props.items);
65
+ useEffect(() => {
66
+ if (!props.random) {
67
+ return;
68
+ }
69
+
70
+ const interval = setInterval(() => {
71
+ setItems((items) => {
72
+ const p = Math.random();
73
+ if (p < 0.5) {
74
+ return items;
75
+ } else if (p < 0.99 && items.length > 1) {
76
+ return items.slice(0, -1);
77
+ } else {
78
+ return [...items, createItem(items[0]?.type)];
79
+ }
80
+ });
81
+ }, 3_000);
82
+
83
+ return () => clearInterval(interval);
84
+ }, []);
85
+
86
+ return (
87
+ <div className='grid grow p-4'>
88
+ <ResponsiveGrid {...props} Cell={TestCell} items={items} pinned={pinned} onPinnedChange={setPinned} />
89
+ </div>
90
+ );
91
+ };
92
+
93
+ const meta = {
60
94
  title: 'plugins/plugin-thread/ResponsiveGrid',
61
- component: ResponsiveGrid,
62
- render: (args) => {
63
- const [pinned, setPinned] = useState<string | undefined>(
64
- (args.pinned ?? args.items.length > 1) ? args.items[0]?.id : undefined,
65
- );
66
- const [items, setItems] = useState<TestItem[]>(args.items);
67
- useEffect(() => {
68
- if (!args.random) {
69
- return;
70
- }
71
-
72
- const interval = setInterval(() => {
73
- setItems((items) => {
74
- const p = Math.random();
75
- if (p < 0.5) {
76
- return items;
77
- } else if (p < 0.99 && items.length > 1) {
78
- return items.slice(0, -1);
79
- } else {
80
- return [...items, createItem(items[0]?.type)];
81
- }
82
- });
83
- }, 3_000);
84
-
85
- return () => clearInterval(interval);
86
- }, []);
87
-
88
- return (
89
- <div className='grid grow p-4'>
90
- <ResponsiveGrid {...args} Cell={TestCell} items={items} pinned={pinned} onPinnedChange={setPinned} />
91
- </div>
92
- );
93
- },
95
+ component: ResponsiveGrid as any,
96
+ render: DefaultStory,
94
97
  decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'justify-center' })],
95
98
  parameters: {
96
99
  translations,
97
100
  },
98
- };
101
+ } satisfies Meta<typeof DefaultStory>;
99
102
 
100
103
  const videoUrls = [
101
104
  'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4',
@@ -117,7 +120,7 @@ const createItem = (type?: 'image' | 'video') => {
117
120
 
118
121
  export default meta;
119
122
 
120
- type Story = StoryObj<StoryProps>;
123
+ type Story = StoryObj<typeof meta>;
121
124
 
122
125
  // TODO(burdon): Story to join/leave repeatedly to test stable position.
123
126
 
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { type ComponentType, type FC, useLayoutEffect, useState, useEffect, useMemo, useCallback } from 'react';
5
+ import React, { type ComponentType, type FC, useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
6
6
  import { useResizeDetector } from 'react-resize-detector';
7
7
 
8
8
  import { invariant } from '@dxos/invariant';
@@ -2,9 +2,9 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { useEffect, useState, type CSSProperties, type PropsWithChildren } from 'react';
5
+ import React, { type CSSProperties, type PropsWithChildren, useEffect, useState } from 'react';
6
6
 
7
- import { Icon, IconButton, useTranslation, type ThemedClassName } from '@dxos/react-ui';
7
+ import { Icon, IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
8
8
  import { Waveform } from '@dxos/react-ui-sfx';
9
9
  import { groupHoverControlItemWithTransition, mx } from '@dxos/react-ui-theme';
10
10
 
@@ -6,23 +6,25 @@ import React, { useCallback, useMemo } from 'react';
6
6
 
7
7
  import {
8
8
  Capabilities,
9
+ CollaborationActions,
9
10
  LayoutAction,
10
11
  createIntent,
11
- useCapability,
12
12
  useCapabilities,
13
+ useCapability,
13
14
  useIntentDispatcher,
14
15
  } from '@dxos/app-framework';
15
16
  import { Filter, Obj, Query, Relation } from '@dxos/echo';
16
- import { fullyQualifiedId, getSpace, useQuery } from '@dxos/react-client/echo';
17
+ import { Ref, fullyQualifiedId, getSpace, useQuery } from '@dxos/react-client/echo';
17
18
  import { useIdentity } from '@dxos/react-client/halo';
18
19
  import { useThemeContext, useTranslation } from '@dxos/react-ui';
19
20
  import { useAttended } from '@dxos/react-ui-attention';
20
21
  import { StackItem } from '@dxos/react-ui-stack';
21
22
  import { Tabs } from '@dxos/react-ui-tabs';
23
+ import { mx } from '@dxos/react-ui-theme';
22
24
  import { AnchoredTo } from '@dxos/schema';
23
25
 
24
26
  import { ThreadCapabilities } from '../capabilities';
25
- import { CommentsContainer } from '../components';
27
+ import { CommentsContainer, type CommentsContainerProps } from '../components';
26
28
  import { meta } from '../meta';
27
29
  import { ThreadAction, ThreadType } from '../types';
28
30
 
@@ -114,6 +116,28 @@ export const ThreadComplementary = ({ subject }: { subject: any }) => {
114
116
  [dispatch, subject],
115
117
  );
116
118
 
119
+ const handleAcceptProposal = useCallback<NonNullable<CommentsContainerProps['onAcceptProposal']>>(
120
+ async (anchor, messageId) => {
121
+ const thread = Relation.getSource(anchor) as ThreadType;
122
+ const messageIndex = thread.messages.findIndex(Ref.hasObjectId(messageId));
123
+ const message = thread.messages[messageIndex]?.target;
124
+ const proposal = message?.blocks.find((block) => block._tag === 'proposal');
125
+ if (!proposal || !anchor.anchor) {
126
+ return;
127
+ }
128
+
129
+ await dispatch(
130
+ createIntent(CollaborationActions.AcceptProposal, {
131
+ subject,
132
+ anchor: anchor.anchor,
133
+ proposal,
134
+ }),
135
+ );
136
+ await dispatch(createIntent(ThreadAction.ToggleResolved, { thread }));
137
+ },
138
+ [dispatch, subject],
139
+ );
140
+
117
141
  const comments = (
118
142
  <CommentsContainer
119
143
  anchors={anchors}
@@ -124,18 +148,22 @@ export const ThreadComplementary = ({ subject }: { subject: any }) => {
124
148
  onResolve={handleResolve}
125
149
  onMessageDelete={handleMessageDelete}
126
150
  onThreadDelete={handleThreadDelete}
151
+ onAcceptProposal={handleAcceptProposal}
127
152
  />
128
153
  );
129
154
 
130
155
  return (
131
- <StackItem.Content toolbar>
156
+ <StackItem.Content toolbar classNames='overflow-hidden'>
132
157
  <Tabs.Root
133
158
  value={showResolvedThreads ? 'all' : 'unresolved'}
134
159
  orientation='horizontal'
135
- classNames='contents [&_[role="tabpanel"]]:min-bs-0 [&_[role="tabpanel"]]:overflow-y-auto [&_[role="tabpanel"]]:scrollbar-thin'
160
+ classNames={[
161
+ 'contents [&_[role="tabpanel"]]:min-bs-0 [&_[role="tabpanel"]]:overflow-y-auto [&_[role="tabpanel"]]:scrollbar-thin',
162
+ ]}
136
163
  onValueChange={onChangeViewState}
137
164
  >
138
- <Tabs.Tablist classNames={tx('toolbar.root', 'toolbar', {})}>
165
+ {/* TODO(burdon): Standardize (like Tollbar). */}
166
+ <Tabs.Tablist classNames={mx('bg-toolbarSurface border-b border-subduedSeparator')}>
139
167
  <Tabs.Tab value='unresolved' classNames='text-sm'>
140
168
  {t('show unresolved label')}
141
169
  </Tabs.Tab>
@@ -143,8 +171,10 @@ export const ThreadComplementary = ({ subject }: { subject: any }) => {
143
171
  {t('show all label')}
144
172
  </Tabs.Tab>
145
173
  </Tabs.Tablist>
146
- <Tabs.Tabpanel value='all'>{showResolvedThreads && comments}</Tabs.Tabpanel>
147
- <Tabs.Tabpanel value='unresolved'>{!showResolvedThreads && comments}</Tabs.Tabpanel>
174
+ <div className='overflow-y-auto'>
175
+ <Tabs.Tabpanel value='all'>{showResolvedThreads && comments}</Tabs.Tabpanel>
176
+ <Tabs.Tabpanel value='unresolved'>{!showResolvedThreads && comments}</Tabs.Tabpanel>
177
+ </div>
148
178
  </Tabs.Root>
149
179
  </StackItem.Content>
150
180
  );
@@ -5,7 +5,7 @@
5
5
  import React from 'react';
6
6
 
7
7
  // import { useTranslation } from '@dxos/react-ui';
8
- import { DeprecatedFormContainer } from '@dxos/react-ui-form';
8
+ import { ControlPage } from '@dxos/react-ui-form';
9
9
 
10
10
  // import { meta } from '../meta';
11
11
  import type { ThreadSettingsProps } from '../types';
@@ -13,5 +13,5 @@ import type { ThreadSettingsProps } from '../types';
13
13
  export const ThreadSettings = ({ settings }: { settings: ThreadSettingsProps }) => {
14
14
  // const { t } = useTranslation(meta.id);
15
15
 
16
- return <DeprecatedFormContainer></DeprecatedFormContainer>;
16
+ return <ControlPage></ControlPage>;
17
17
  };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { StreamLanguage, HighlightStyle, syntaxHighlighting } from '@codemirror/language';
5
+ import { HighlightStyle, StreamLanguage, syntaxHighlighting } from '@codemirror/language';
6
6
  import { type Extension } from '@codemirror/state';
7
7
 
8
8
  import { tags } from '@dxos/react-ui-editor';
@@ -27,7 +27,10 @@ const parser = StreamLanguage.define<{ count: number }>({
27
27
  });
28
28
 
29
29
  const styles = HighlightStyle.define([
30
- { tag: tags.tagName, class: mx('dx-tag dx-tag--indigo text-baseText font-medium') },
30
+ {
31
+ tag: tags.tagName,
32
+ class: mx('dx-tag dx-tag--indigo text-baseText font-medium'),
33
+ },
31
34
  ]);
32
35
 
33
36
  export const command: Extension = [parser, syntaxHighlighting(styles)];
@@ -12,6 +12,7 @@ import { ThreadType } from '../types';
12
12
 
13
13
  export const createCommentThread = (identity: Identity): ThreadType => {
14
14
  return Obj.make(ThreadType, {
15
+ name: 'Comment',
15
16
  messages: faker.helpers.multiple(
16
17
  () =>
17
18
  Ref.make(
@@ -25,5 +26,26 @@ export const createCommentThread = (identity: Identity): ThreadType => {
25
26
  ),
26
27
  { count: { min: 2, max: 3 } },
27
28
  ),
29
+ status: 'active',
30
+ });
31
+ };
32
+
33
+ export const createProposalThread = (identity: Identity): ThreadType => {
34
+ return Obj.make(ThreadType, {
35
+ name: 'Proposal',
36
+ messages: faker.helpers.multiple(
37
+ () =>
38
+ Ref.make(
39
+ Obj.make(DataType.Message, {
40
+ created: new Date().toISOString(),
41
+ sender: {
42
+ identityDid: faker.datatype.boolean() ? identity.did : IdentityDid.random(),
43
+ },
44
+ blocks: [{ _tag: 'proposal', text: faker.lorem.sentences(3) }],
45
+ }),
46
+ ),
47
+ { count: { min: 1, max: 1 } },
48
+ ),
49
+ status: 'active',
28
50
  });
29
51
  };
@@ -6,17 +6,17 @@ import { type Extension } from '@codemirror/state';
6
6
  import { EditorView } from '@codemirror/view';
7
7
  import { computed, effect } from '@preact/signals-core';
8
8
 
9
- import { createIntent, type PromiseIntentDispatcher } from '@dxos/app-framework';
9
+ import { type PromiseIntentDispatcher, createIntent } from '@dxos/app-framework';
10
10
  import { Filter, Obj, Query, Relation } from '@dxos/echo';
11
- import { type DocumentType } from '@dxos/plugin-markdown/types';
12
- import { getSpace, getTextInRange, createDocAccessor, fullyQualifiedId, getSource } from '@dxos/react-client/echo';
11
+ import { type Markdown } from '@dxos/plugin-markdown/types';
12
+ import { createDocAccessor, fullyQualifiedId, getSource, getSpace, getTextInRange } from '@dxos/react-client/echo';
13
13
  import { comments, createExternalCommentSync } from '@dxos/react-ui-editor';
14
14
  import { AnchoredTo } from '@dxos/schema';
15
15
 
16
- import { ThreadAction, ThreadType, type ThreadState } from '../types';
16
+ import { ThreadAction, type ThreadState, ThreadType } from '../types';
17
17
 
18
18
  // TODO(burdon): Factor out.
19
- const getName = (doc: DocumentType, anchor: string): string | undefined => {
19
+ const getName = (doc: Markdown.Document, anchor: string): string | undefined => {
20
20
  if (doc.content) {
21
21
  const [start, end] = anchor.split(':');
22
22
  return getTextInRange(createDocAccessor(doc.content.target!, ['content']), start, end);
@@ -26,7 +26,7 @@ const getName = (doc: DocumentType, anchor: string): string | undefined => {
26
26
  /**
27
27
  * Construct plugins.
28
28
  */
29
- export const threads = (state: ThreadState, doc?: DocumentType, dispatch?: PromiseIntentDispatcher): Extension => {
29
+ export const threads = (state: ThreadState, doc?: Markdown.Document, dispatch?: PromiseIntentDispatcher): Extension => {
30
30
  const space = doc && getSpace(doc);
31
31
  if (!doc || !space || !dispatch) {
32
32
  // Include no-op comments extension here to ensure that the facets are always present when they are expected.
@@ -7,3 +7,4 @@ export * from './useDebugMode';
7
7
  export * from './useStatus';
8
8
  export * from './useVideoStreamTrack';
9
9
  export * from './useInaudibleAudioStreamTrack';
10
+ export * from './useBlackCanvasStreamTrack';
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useEffect, useState } from 'react';
6
+
7
+ import { scheduleTask } from '@dxos/async';
8
+ import { Context } from '@dxos/context';
9
+
10
+ import { createBlackCanvasStreamTrack } from '../calls/util/stub-media-streams';
11
+
12
+ export const useBlackCanvasStreamTrack = () => {
13
+ const [blackCanvasStreamTrack, setBlackCanvasStreamTrack] = useState<MediaStreamTrack | undefined>(undefined);
14
+ useEffect(() => {
15
+ const ctx = new Context();
16
+ scheduleTask(ctx, async () => {
17
+ const blackCanvasStreamTrack = await createBlackCanvasStreamTrack({ ctx, width: 720, height: 480 });
18
+ setBlackCanvasStreamTrack(blackCanvasStreamTrack);
19
+ });
20
+
21
+ return () => {
22
+ void ctx.dispose();
23
+ };
24
+ }, []);
25
+
26
+ return blackCanvasStreamTrack;
27
+ };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { useState, useEffect } from 'react';
5
+ import { useEffect, useState } from 'react';
6
6
 
7
7
  // TODO(burdon): Standardize across plugins/composer.
8
8
  export const useDebugMode = () => {