@dxos/plugin-thread 0.8.1-main.ba2dec9 → 0.8.1-staging.31c3ee1

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 (179) hide show
  1. package/dist/lib/browser/ChatContainer-IOOAQW6S.mjs +138 -0
  2. package/dist/lib/browser/ChatContainer-IOOAQW6S.mjs.map +7 -0
  3. package/dist/lib/browser/app-graph-builder-6W4UKJDI.mjs +41 -0
  4. package/dist/lib/browser/app-graph-builder-6W4UKJDI.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-NAO3LZMZ.mjs → chunk-EP6MRLY7.mjs} +2 -2
  6. package/dist/lib/browser/{chunk-BNEWNQBO.mjs → chunk-QDJ3Y6WF.mjs} +14 -19
  7. package/dist/lib/browser/{chunk-BNEWNQBO.mjs.map → chunk-QDJ3Y6WF.mjs.map} +3 -3
  8. package/dist/lib/browser/chunk-S5ZR4FYW.mjs +17 -0
  9. package/dist/lib/browser/chunk-S5ZR4FYW.mjs.map +7 -0
  10. package/dist/lib/browser/{chunk-UPKU23DA.mjs → chunk-TFVPUPOO.mjs} +35 -140
  11. package/dist/lib/browser/chunk-TFVPUPOO.mjs.map +7 -0
  12. package/dist/lib/browser/{chunk-636QDHE3.mjs → chunk-WBJLUUOK.mjs} +1 -1
  13. package/dist/lib/browser/{chunk-636QDHE3.mjs.map → chunk-WBJLUUOK.mjs.map} +2 -2
  14. package/dist/lib/browser/{chunk-YK3ICB7G.mjs → chunk-WOG72DWE.mjs} +25 -11
  15. package/dist/lib/browser/chunk-WOG72DWE.mjs.map +7 -0
  16. package/dist/lib/browser/index.mjs +47 -49
  17. package/dist/lib/browser/index.mjs.map +4 -4
  18. package/dist/lib/browser/{intent-resolver-FAFQ54NP.mjs → intent-resolver-QAY4FLLM.mjs} +134 -94
  19. package/dist/lib/browser/intent-resolver-QAY4FLLM.mjs.map +7 -0
  20. package/dist/lib/browser/{markdown-RPYOUBVR.mjs → markdown-7KXBXCUS.mjs} +9 -10
  21. package/dist/lib/browser/markdown-7KXBXCUS.mjs.map +7 -0
  22. package/dist/lib/browser/meta.json +1 -1
  23. package/dist/lib/browser/{react-surface-Y4MUSICN.mjs → react-surface-MT3OWULR.mjs} +55 -51
  24. package/dist/lib/browser/react-surface-MT3OWULR.mjs.map +7 -0
  25. package/dist/lib/browser/{settings-WQTE5AEZ.mjs → settings-4FEC7EAX.mjs} +3 -3
  26. package/dist/lib/browser/{state-GNFUURIC.mjs → state-PNMSLKAH.mjs} +4 -3
  27. package/dist/lib/browser/state-PNMSLKAH.mjs.map +7 -0
  28. package/dist/lib/browser/types.mjs +2 -2
  29. package/dist/lib/node/ChatContainer-4FNDRF3G.cjs +163 -0
  30. package/dist/lib/node/ChatContainer-4FNDRF3G.cjs.map +7 -0
  31. package/dist/lib/node/app-graph-builder-HZNWT4QU.cjs +57 -0
  32. package/dist/lib/node/app-graph-builder-HZNWT4QU.cjs.map +7 -0
  33. package/dist/lib/node/{chunk-CMEMMYDL.cjs → chunk-46BSFPBV.cjs} +29 -15
  34. package/dist/lib/node/chunk-46BSFPBV.cjs.map +7 -0
  35. package/dist/lib/node/{chunk-AWSTZ2KN.cjs → chunk-AKINTFBH.cjs} +17 -23
  36. package/dist/lib/node/chunk-AKINTFBH.cjs.map +7 -0
  37. package/dist/lib/node/chunk-E7Z3BKRQ.cjs +232 -0
  38. package/dist/lib/node/chunk-E7Z3BKRQ.cjs.map +7 -0
  39. package/dist/lib/node/{chunk-FPWNZ6TB.cjs → chunk-NQ5DTPOY.cjs} +7 -7
  40. package/dist/lib/node/{chunk-F735VMTN.cjs → chunk-ONLWOUYZ.cjs} +4 -4
  41. package/dist/lib/node/{chunk-F735VMTN.cjs.map → chunk-ONLWOUYZ.cjs.map} +2 -2
  42. package/dist/lib/node/{chunk-BHFHFTHO.cjs → chunk-S2A6BXEA.cjs} +12 -9
  43. package/dist/lib/node/chunk-S2A6BXEA.cjs.map +7 -0
  44. package/dist/lib/node/index.cjs +78 -83
  45. package/dist/lib/node/index.cjs.map +4 -4
  46. package/dist/lib/node/{intent-resolver-DVBEXJ33.cjs → intent-resolver-S6FE5OR7.cjs} +153 -113
  47. package/dist/lib/node/intent-resolver-S6FE5OR7.cjs.map +7 -0
  48. package/dist/lib/node/{markdown-ELRGISXE.cjs → markdown-L2WMCQKM.cjs} +13 -14
  49. package/dist/lib/node/markdown-L2WMCQKM.cjs.map +7 -0
  50. package/dist/lib/node/meta.json +1 -1
  51. package/dist/lib/node/{react-surface-7HLTN5XS.cjs → react-surface-Z45BYECE.cjs} +68 -64
  52. package/dist/lib/node/react-surface-Z45BYECE.cjs.map +7 -0
  53. package/dist/lib/node/{settings-7VQYARM5.cjs → settings-IXCPE55J.cjs} +8 -8
  54. package/dist/lib/node/{state-TQWEZ6B3.cjs → state-AT73KPGZ.cjs} +8 -7
  55. package/dist/lib/node/state-AT73KPGZ.cjs.map +7 -0
  56. package/dist/lib/node/types.cjs +4 -4
  57. package/dist/lib/node/types.cjs.map +1 -1
  58. package/dist/lib/node-esm/ChatContainer-NYLCL6QA.mjs +139 -0
  59. package/dist/lib/node-esm/ChatContainer-NYLCL6QA.mjs.map +7 -0
  60. package/dist/lib/node-esm/app-graph-builder-M3KK5QJH.mjs +42 -0
  61. package/dist/lib/node-esm/app-graph-builder-M3KK5QJH.mjs.map +7 -0
  62. package/dist/lib/node-esm/{chunk-FXUATCLW.mjs → chunk-42F2DTMM.mjs} +1 -1
  63. package/dist/lib/node-esm/{chunk-FXUATCLW.mjs.map → chunk-42F2DTMM.mjs.map} +2 -2
  64. package/dist/lib/node-esm/{chunk-CISLDOH7.mjs → chunk-ADTVMESP.mjs} +35 -140
  65. package/dist/lib/node-esm/chunk-ADTVMESP.mjs.map +7 -0
  66. package/dist/lib/node-esm/{chunk-33NDMQQQ.mjs → chunk-E446MGLP.mjs} +25 -11
  67. package/dist/lib/node-esm/chunk-E446MGLP.mjs.map +7 -0
  68. package/dist/lib/node-esm/{chunk-HLWJTUWZ.mjs → chunk-J56FORYF.mjs} +14 -19
  69. package/dist/lib/node-esm/{chunk-HLWJTUWZ.mjs.map → chunk-J56FORYF.mjs.map} +3 -3
  70. package/dist/lib/node-esm/{chunk-ZJITX3TO.mjs → chunk-WLY2HMDB.mjs} +2 -2
  71. package/dist/lib/node-esm/chunk-WSE23GSG.mjs +19 -0
  72. package/dist/lib/node-esm/chunk-WSE23GSG.mjs.map +7 -0
  73. package/dist/lib/node-esm/index.mjs +47 -49
  74. package/dist/lib/node-esm/index.mjs.map +4 -4
  75. package/dist/lib/node-esm/{intent-resolver-DHVLBNCC.mjs → intent-resolver-OURFRSSF.mjs} +134 -94
  76. package/dist/lib/node-esm/intent-resolver-OURFRSSF.mjs.map +7 -0
  77. package/dist/lib/node-esm/{markdown-23OB7EZG.mjs → markdown-ZKU23UVD.mjs} +9 -10
  78. package/dist/lib/node-esm/markdown-ZKU23UVD.mjs.map +7 -0
  79. package/dist/lib/node-esm/meta.json +1 -1
  80. package/dist/lib/node-esm/{react-surface-AZHTTR6J.mjs → react-surface-KIJ767JK.mjs} +55 -51
  81. package/dist/lib/node-esm/react-surface-KIJ767JK.mjs.map +7 -0
  82. package/dist/lib/node-esm/{settings-SGMM7UDT.mjs → settings-ZUJQ4LWI.mjs} +3 -3
  83. package/dist/lib/node-esm/{state-7MLR27YG.mjs → state-SSDX5O67.mjs} +4 -3
  84. package/dist/lib/node-esm/state-SSDX5O67.mjs.map +7 -0
  85. package/dist/lib/node-esm/types.mjs +2 -2
  86. package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
  87. package/dist/types/src/capabilities/app-graph-builder.d.ts +181 -0
  88. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  89. package/dist/types/src/capabilities/index.d.ts +177 -0
  90. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  91. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  92. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  93. package/dist/types/src/components/Chat.stories.d.ts.map +1 -1
  94. package/dist/types/src/components/ChatContainer.d.ts +8 -1
  95. package/dist/types/src/components/ChatContainer.d.ts.map +1 -1
  96. package/dist/types/src/components/CommentContainer.d.ts.map +1 -1
  97. package/dist/types/src/components/Comments.stories.d.ts.map +1 -1
  98. package/dist/types/src/components/MessageContainer.d.ts +3 -2
  99. package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
  100. package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -1
  101. package/dist/types/src/components/index.d.ts +1 -4
  102. package/dist/types/src/components/index.d.ts.map +1 -1
  103. package/dist/types/src/components/testing.d.ts +45 -40
  104. package/dist/types/src/components/testing.d.ts.map +1 -1
  105. package/dist/types/src/events.d.ts +4 -0
  106. package/dist/types/src/events.d.ts.map +1 -0
  107. package/dist/types/src/hooks/analytics.d.ts +2 -2
  108. package/dist/types/src/hooks/analytics.d.ts.map +1 -1
  109. package/dist/types/src/index.d.ts +2 -0
  110. package/dist/types/src/index.d.ts.map +1 -1
  111. package/dist/types/src/meta.d.ts +2 -7
  112. package/dist/types/src/meta.d.ts.map +1 -1
  113. package/dist/types/src/translations.d.ts +5 -12
  114. package/dist/types/src/translations.d.ts.map +1 -1
  115. package/dist/types/src/types.d.ts +40 -9
  116. package/dist/types/src/types.d.ts.map +1 -1
  117. package/package.json +33 -30
  118. package/src/ThreadPlugin.tsx +17 -28
  119. package/src/capabilities/app-graph-builder.ts +34 -0
  120. package/src/capabilities/index.ts +1 -0
  121. package/src/capabilities/intent-resolver.ts +97 -61
  122. package/src/capabilities/react-surface.tsx +29 -27
  123. package/src/capabilities/state.ts +1 -1
  124. package/src/components/Chat.stories.tsx +19 -9
  125. package/src/components/ChatContainer.tsx +29 -29
  126. package/src/components/CommentContainer.tsx +8 -2
  127. package/src/components/Comments.stories.tsx +2 -1
  128. package/src/components/MessageContainer.tsx +23 -17
  129. package/src/components/ThreadComplementary.tsx +26 -32
  130. package/src/components/index.ts +1 -2
  131. package/src/components/testing.ts +6 -29
  132. package/src/events.ts +11 -0
  133. package/src/extensions/threads.ts +3 -3
  134. package/src/hooks/analytics.ts +23 -18
  135. package/src/index.ts +2 -0
  136. package/src/meta.ts +2 -2
  137. package/src/translations.ts +2 -4
  138. package/src/types.ts +22 -8
  139. package/src/util.ts +1 -1
  140. package/dist/lib/browser/ThreadContainer-6OP5AG2W.mjs +0 -19
  141. package/dist/lib/browser/ThreadContainer-6OP5AG2W.mjs.map +0 -7
  142. package/dist/lib/browser/chunk-UPKU23DA.mjs.map +0 -7
  143. package/dist/lib/browser/chunk-VOMIDDNO.mjs +0 -15
  144. package/dist/lib/browser/chunk-VOMIDDNO.mjs.map +0 -7
  145. package/dist/lib/browser/chunk-YK3ICB7G.mjs.map +0 -7
  146. package/dist/lib/browser/intent-resolver-FAFQ54NP.mjs.map +0 -7
  147. package/dist/lib/browser/markdown-RPYOUBVR.mjs.map +0 -7
  148. package/dist/lib/browser/react-surface-Y4MUSICN.mjs.map +0 -7
  149. package/dist/lib/browser/state-GNFUURIC.mjs.map +0 -7
  150. package/dist/lib/node/ThreadContainer-LTKKLPXX.cjs +0 -45
  151. package/dist/lib/node/ThreadContainer-LTKKLPXX.cjs.map +0 -7
  152. package/dist/lib/node/chunk-AWSTZ2KN.cjs.map +0 -7
  153. package/dist/lib/node/chunk-BHFHFTHO.cjs.map +0 -7
  154. package/dist/lib/node/chunk-CMEMMYDL.cjs.map +0 -7
  155. package/dist/lib/node/chunk-RQR5Z23G.cjs +0 -331
  156. package/dist/lib/node/chunk-RQR5Z23G.cjs.map +0 -7
  157. package/dist/lib/node/intent-resolver-DVBEXJ33.cjs.map +0 -7
  158. package/dist/lib/node/markdown-ELRGISXE.cjs.map +0 -7
  159. package/dist/lib/node/react-surface-7HLTN5XS.cjs.map +0 -7
  160. package/dist/lib/node/state-TQWEZ6B3.cjs.map +0 -7
  161. package/dist/lib/node-esm/ThreadContainer-SV74JFRH.mjs +0 -20
  162. package/dist/lib/node-esm/ThreadContainer-SV74JFRH.mjs.map +0 -7
  163. package/dist/lib/node-esm/chunk-33NDMQQQ.mjs.map +0 -7
  164. package/dist/lib/node-esm/chunk-CISLDOH7.mjs.map +0 -7
  165. package/dist/lib/node-esm/chunk-PJPVEO7E.mjs +0 -17
  166. package/dist/lib/node-esm/chunk-PJPVEO7E.mjs.map +0 -7
  167. package/dist/lib/node-esm/intent-resolver-DHVLBNCC.mjs.map +0 -7
  168. package/dist/lib/node-esm/markdown-23OB7EZG.mjs.map +0 -7
  169. package/dist/lib/node-esm/react-surface-AZHTTR6J.mjs.map +0 -7
  170. package/dist/lib/node-esm/state-7MLR27YG.mjs.map +0 -7
  171. package/dist/types/src/components/ThreadContainer.d.ts +0 -7
  172. package/dist/types/src/components/ThreadContainer.d.ts.map +0 -1
  173. package/src/components/ThreadContainer.tsx +0 -22
  174. /package/dist/lib/browser/{chunk-NAO3LZMZ.mjs.map → chunk-EP6MRLY7.mjs.map} +0 -0
  175. /package/dist/lib/browser/{settings-WQTE5AEZ.mjs.map → settings-4FEC7EAX.mjs.map} +0 -0
  176. /package/dist/lib/node/{chunk-FPWNZ6TB.cjs.map → chunk-NQ5DTPOY.cjs.map} +0 -0
  177. /package/dist/lib/node/{settings-7VQYARM5.cjs.map → settings-IXCPE55J.cjs.map} +0 -0
  178. /package/dist/lib/node-esm/{chunk-ZJITX3TO.mjs.map → chunk-WLY2HMDB.mjs.map} +0 -0
  179. /package/dist/lib/node-esm/{settings-SGMM7UDT.mjs.map → settings-ZUJQ4LWI.mjs.map} +0 -0
@@ -2,19 +2,17 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import {
6
- Capabilities,
7
- contributes,
8
- createIntent,
9
- createResolver,
10
- LayoutAction,
11
- type PluginsContext,
12
- } from '@dxos/app-framework';
13
- import { Ref } from '@dxos/echo-schema';
5
+ import { Capabilities, contributes, createIntent, createResolver, type PluginsContext } from '@dxos/app-framework';
6
+ import { ObjectId, Ref } from '@dxos/echo-schema';
7
+ import { invariant } from '@dxos/invariant';
8
+ import { DXN, QueueSubspaceTags } from '@dxos/keys';
9
+ import { refFromDXN } from '@dxos/live-object';
14
10
  import { log } from '@dxos/log';
11
+ import { ATTENDABLE_PATH_SEPARATOR, DeckAction } from '@dxos/plugin-deck/types';
15
12
  import { ObservabilityAction } from '@dxos/plugin-observability/types';
16
13
  import { ChannelType, ThreadType } from '@dxos/plugin-space/types';
17
14
  import { create, fullyQualifiedId, getSpace, makeRef } from '@dxos/react-client/echo';
15
+ import { MessageType } from '@dxos/schema';
18
16
 
19
17
  import { ThreadCapabilities } from './capabilities';
20
18
  import { THREAD_PLUGIN } from '../meta';
@@ -22,57 +20,58 @@ import { ThreadAction } from '../types';
22
20
 
23
21
  export default (context: PluginsContext) =>
24
22
  contributes(Capabilities.IntentResolver, [
23
+ createResolver({
24
+ intent: ThreadAction.CreateChannel,
25
+ resolve: ({ spaceId, name }) => ({
26
+ data: {
27
+ object: create(ChannelType, {
28
+ name,
29
+ queue: refFromDXN(new DXN(DXN.kind.QUEUE, [QueueSubspaceTags.DATA, spaceId, ObjectId.random()])),
30
+ }),
31
+ },
32
+ }),
33
+ }),
25
34
  createResolver({
26
35
  intent: ThreadAction.Create,
27
36
  resolve: ({ name, cursor, subject }) => {
28
- if (cursor && subject) {
29
- // Seed the threads array if it does not exist.
30
- if (subject?.threads === undefined) {
31
- try {
32
- // Static schema will throw an error if subject does not support threads array property.
33
- subject.threads = [];
34
- } catch (err) {
35
- log.error('Subject does not support threads array', { typename: subject?.typename });
36
- return;
37
- }
38
- }
39
-
40
- const { state } = context.requestCapability(ThreadCapabilities.MutableState);
41
- const subjectId = fullyQualifiedId(subject);
42
- const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
43
- const draft = state.drafts[subjectId];
44
- if (draft) {
45
- draft.push(thread);
46
- } else {
47
- state.drafts[subjectId] = [thread];
37
+ // Seed the threads array if it does not exist.
38
+ if (subject?.threads === undefined) {
39
+ try {
40
+ // Static schema will throw an error if subject does not support threads array property.
41
+ subject.threads = [];
42
+ } catch (err) {
43
+ log.error('Subject does not support threads array', { typename: subject?.typename });
44
+ return;
48
45
  }
46
+ }
49
47
 
50
- return {
51
- data: { object: thread },
52
- intents: [
53
- createIntent(ThreadAction.Select, { current: fullyQualifiedId(thread) }),
54
- createIntent(LayoutAction.UpdateComplementary, { part: 'complementary', subject: 'comments' }),
55
- ],
56
- };
48
+ const { state } = context.requestCapability(ThreadCapabilities.MutableState);
49
+ const subjectId = fullyQualifiedId(subject);
50
+ const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
51
+ const draft = state.drafts[subjectId];
52
+ if (draft) {
53
+ draft.push(thread);
57
54
  } else {
58
- // TODO(wittjosiah): Create separate intent for channel creation.
59
- return {
60
- data: { object: create(ChannelType, { threads: [makeRef(create(ThreadType, { messages: [] }))] }) },
61
- };
55
+ state.drafts[subjectId] = [thread];
62
56
  }
57
+
58
+ return {
59
+ data: { object: thread },
60
+ intents: [
61
+ createIntent(ThreadAction.Select, { current: fullyQualifiedId(thread) }),
62
+ createIntent(DeckAction.ChangeCompanion, {
63
+ primary: subjectId,
64
+ companion: `${subjectId}${ATTENDABLE_PATH_SEPARATOR}comments`,
65
+ }),
66
+ ],
67
+ };
63
68
  },
64
69
  }),
65
70
  createResolver({
66
71
  intent: ThreadAction.Select,
67
- resolve: ({ current, skipOpen }) => {
72
+ resolve: ({ current }) => {
68
73
  const { state } = context.requestCapability(ThreadCapabilities.MutableState);
69
74
  state.current = current;
70
-
71
- return {
72
- intents: !skipOpen
73
- ? [createIntent(LayoutAction.UpdateComplementary, { part: 'complementary', subject: 'comments' })]
74
- : undefined,
75
- };
76
75
  },
77
76
  }),
78
77
  createResolver({
@@ -85,13 +84,17 @@ export default (context: PluginsContext) =>
85
84
  }
86
85
 
87
86
  const space = getSpace(thread);
88
- const spaceId = space?.id;
87
+ invariant(space, 'Space not found');
88
+ const spaceId = space.id;
89
89
 
90
90
  return {
91
91
  intents: [
92
92
  createIntent(ObservabilityAction.SendEvent, {
93
93
  name: 'threads.toggle-resolved',
94
- properties: { threadId: thread.id, spaceId },
94
+ properties: {
95
+ spaceId,
96
+ threadId: thread.id,
97
+ },
95
98
  }),
96
99
  ],
97
100
  };
@@ -134,7 +137,10 @@ export default (context: PluginsContext) =>
134
137
  intents: [
135
138
  createIntent(ObservabilityAction.SendEvent, {
136
139
  name: 'threads.delete',
137
- properties: { threadId: thread.id, spaceId: space.id },
140
+ properties: {
141
+ spaceId: space.id,
142
+ threadId: thread.id,
143
+ },
138
144
  }),
139
145
  ],
140
146
  };
@@ -147,7 +153,10 @@ export default (context: PluginsContext) =>
147
153
  intents: [
148
154
  createIntent(ObservabilityAction.SendEvent, {
149
155
  name: 'threads.undo-delete',
150
- properties: { threadId: thread.id, spaceId: space.id },
156
+ properties: {
157
+ spaceId: space.id,
158
+ threadId: thread.id,
159
+ },
151
160
  }),
152
161
  ],
153
162
  };
@@ -155,13 +164,22 @@ export default (context: PluginsContext) =>
155
164
  },
156
165
  }),
157
166
  createResolver({
158
- intent: ThreadAction.OnMessageAdd,
159
- resolve: ({ thread, subject }) => {
167
+ intent: ThreadAction.AddMessage,
168
+ resolve: ({ thread, subject, sender, text }) => {
160
169
  const { state } = context.requestCapability(ThreadCapabilities.MutableState);
161
170
  const subjectId = fullyQualifiedId(subject);
162
171
  const space = getSpace(subject);
172
+ invariant(space, 'Space not found');
163
173
  const intents = [];
164
- const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
174
+
175
+ const message = create(MessageType, {
176
+ sender,
177
+ created: new Date().toISOString(),
178
+ blocks: [{ type: 'text', text }],
179
+ // TODO(wittjosiah): Context based on attention.
180
+ // context: context ? makeRef(context) : undefined,
181
+ });
182
+ thread.messages.push(makeRef(message));
165
183
 
166
184
  if (state.drafts[subjectId]?.find((t) => t === thread)) {
167
185
  // Move draft to document.
@@ -170,16 +188,25 @@ export default (context: PluginsContext) =>
170
188
  state.drafts[subjectId] = state.drafts[subjectId]?.filter(({ id }) => id !== thread.id);
171
189
  intents.push(
172
190
  createIntent(ObservabilityAction.SendEvent, {
173
- name: 'threads.thread-created',
174
- properties: analyticsProperties,
191
+ name: 'threads.create',
192
+ properties: {
193
+ spaceId: space.id,
194
+ threadId: thread.id,
195
+ },
175
196
  }),
176
197
  );
177
198
  }
178
199
 
179
200
  intents.push(
180
201
  createIntent(ObservabilityAction.SendEvent, {
181
- name: 'threads.message-added',
182
- properties: { ...analyticsProperties, threadLength: thread.messages.length },
202
+ name: 'threads.message.add',
203
+ properties: {
204
+ spaceId: space.id,
205
+ threadId: thread.id,
206
+ threadLength: thread.messages.length,
207
+ messageId: message.id,
208
+ messageLength: text.length,
209
+ },
183
210
  }),
184
211
  );
185
212
 
@@ -190,6 +217,7 @@ export default (context: PluginsContext) =>
190
217
  intent: ThreadAction.DeleteMessage,
191
218
  resolve: ({ subject, thread, messageId, message, messageIndex }, undo) => {
192
219
  const space = getSpace(subject);
220
+ invariant(space, 'Space not found');
193
221
 
194
222
  if (!undo) {
195
223
  const messageIndex = thread.messages.findIndex(Ref.hasObjectId(messageId));
@@ -215,12 +243,16 @@ export default (context: PluginsContext) =>
215
243
  intents: [
216
244
  createIntent(ObservabilityAction.SendEvent, {
217
245
  name: 'threads.message.delete',
218
- properties: { threadId: thread.id, spaceId: space?.id },
246
+ properties: {
247
+ spaceId: space.id,
248
+ threadId: thread.id,
249
+ messageId: message.id,
250
+ },
219
251
  }),
220
252
  ],
221
253
  };
222
254
  } else {
223
- if (!messageIndex || !message) {
255
+ if (messageIndex === undefined || !message) {
224
256
  return;
225
257
  }
226
258
 
@@ -229,7 +261,11 @@ export default (context: PluginsContext) =>
229
261
  intents: [
230
262
  createIntent(ObservabilityAction.SendEvent, {
231
263
  name: 'threads.message.undo-delete',
232
- properties: { threadId: thread.id, spaceId: space?.id },
264
+ properties: {
265
+ spaceId: space.id,
266
+ threadId: thread.id,
267
+ messageId: message.id,
268
+ },
233
269
  }),
234
270
  ],
235
271
  };
@@ -6,11 +6,12 @@ import React from 'react';
6
6
 
7
7
  import { Capabilities, contributes, createSurface, useLayout } from '@dxos/app-framework';
8
8
  import { type Ref } from '@dxos/echo-schema';
9
+ import { DXN } from '@dxos/keys';
9
10
  import { SettingsStore } from '@dxos/local-storage';
10
11
  import { ChannelType, type ThreadType } from '@dxos/plugin-space/types';
11
- import { getSpace } from '@dxos/react-client/echo';
12
+ import { getSpace, isSpace, type Space } from '@dxos/react-client/echo';
12
13
 
13
- import { ThreadContainer, ThreadComplementary, ThreadSettings } from '../components';
14
+ import { ChatContainer, ThreadComplementary, ThreadSettings } from '../components';
14
15
  import { THREAD_PLUGIN } from '../meta';
15
16
  import { type ThreadSettingsProps } from '../types';
16
17
 
@@ -19,40 +20,41 @@ export default () =>
19
20
  createSurface({
20
21
  id: `${THREAD_PLUGIN}/channel`,
21
22
  role: 'article',
22
- filter: (data): data is { subject: ChannelType } =>
23
- data.subject instanceof ChannelType && !!data.subject.threads[0],
24
- component: ({ data, role }) => {
23
+ filter: (data): data is { subject: ChannelType } => data.subject instanceof ChannelType,
24
+ component: ({ data: { subject: channel }, role }) => {
25
25
  const layout = useLayout();
26
- const channel = data.subject;
27
- void channel.threads[0].load();
28
- const thread = channel.threads[0].target;
29
- if (!thread) {
30
- return null;
31
- }
32
-
33
26
  const currentPosition = layout.active.findIndex((id) => id === channel.id);
34
- if (currentPosition > 0) {
35
- const objectToTheLeft = layout.active[currentPosition - 1];
36
- const context = getSpace(channel)?.db.getObjectById(objectToTheLeft);
37
- return <ThreadContainer role={role} thread={thread} context={context} />;
27
+ const objectToTheLeft = layout.active[currentPosition - 1];
28
+ const context = currentPosition > 0 ? getSpace(channel)?.db.getObjectById(objectToTheLeft) : undefined;
29
+ const space = getSpace(channel);
30
+ if (!space) {
31
+ return null;
38
32
  }
39
33
 
40
- return <ThreadContainer role={role} thread={thread} />;
34
+ return <ChatContainer space={space} dxn={channel.queue.dxn} context={context} />;
41
35
  },
42
36
  }),
43
37
  createSurface({
44
- id: `${THREAD_PLUGIN}/thread`,
45
- role: 'complementary--comments',
46
- filter: (data): data is { subject: { threads: Ref<ThreadType>[] } } =>
47
- !!data.subject &&
48
- typeof data.subject === 'object' &&
49
- 'threads' in data.subject &&
50
- Array.isArray(data.subject.threads) &&
51
- !(data.subject instanceof ChannelType),
52
- component: ({ data }) => <ThreadComplementary subject={data.subject} />,
38
+ id: `${THREAD_PLUGIN}/comments`,
39
+ role: 'article',
40
+ filter: (data): data is { companionTo: { threads: Ref<ThreadType>[] } } =>
41
+ data.subject === 'comments' &&
42
+ !!data.companionTo &&
43
+ typeof data.companionTo === 'object' &&
44
+ 'threads' in data.companionTo &&
45
+ Array.isArray(data.companionTo.threads),
46
+ // TODO(wittjosiah): This isn't scrolling properly in a plank.
47
+ component: ({ data }) => <ThreadComplementary subject={data.companionTo} />,
48
+ }),
49
+ createSurface({
50
+ id: `${THREAD_PLUGIN}/tabpanel`,
51
+ role: 'tabpanel',
52
+ filter: (data): data is { subject: DXN; space: Space } =>
53
+ data.subject instanceof DXN && data.type === 'chat' && isSpace(data.space),
54
+ component: ({ data: { subject: dxn, space } }) => <ChatContainer dxn={dxn} space={space} />,
53
55
  }),
54
56
  createSurface({
55
- id: `${THREAD_PLUGIN}/settings`,
57
+ id: `${THREAD_PLUGIN}/plugin-settings`,
56
58
  role: 'article',
57
59
  filter: (data): data is { subject: SettingsStore<ThreadSettingsProps> } =>
58
60
  data.subject instanceof SettingsStore && data.subject.prefix === THREAD_PLUGIN,
@@ -11,7 +11,7 @@ import { type ThreadState, type ViewStore } from '../types';
11
11
  const initialViewState = { showResolvedThreads: false };
12
12
 
13
13
  export default () => {
14
- const state = create<ThreadState>({ drafts: {} });
14
+ const state = create<ThreadState>({ activities: {}, drafts: {} });
15
15
  const viewStore = create<ViewStore>({});
16
16
 
17
17
  const getViewState = (subjectId: string) => {
@@ -7,19 +7,22 @@ import '@dxos-theme';
7
7
  import { type Meta } from '@storybook/react';
8
8
  import React, { useEffect, useState } from 'react';
9
9
 
10
- import { Capabilities, contributes, createSurface } from '@dxos/app-framework';
10
+ import { Capabilities, contributes, createSurface, IntentPlugin } from '@dxos/app-framework';
11
11
  import { withPluginManager } from '@dxos/app-framework/testing';
12
- import { MessageType, ThreadType } from '@dxos/plugin-space/types';
12
+ import { ObjectId } from '@dxos/echo-schema';
13
+ import { DXN, QueueSubspaceTags } from '@dxos/keys';
14
+ import { refFromDXN } from '@dxos/live-object';
15
+ import { ChannelType, ThreadType } from '@dxos/plugin-space/types';
13
16
  import { faker } from '@dxos/random';
14
17
  import { useClient } from '@dxos/react-client';
15
- import { type Space } from '@dxos/react-client/echo';
18
+ import { create, type Space } from '@dxos/react-client/echo';
16
19
  import { useIdentity } from '@dxos/react-client/halo';
17
20
  import { withClientProvider } from '@dxos/react-client/testing';
18
21
  import { Thread } from '@dxos/react-ui-thread';
22
+ import { MessageType } from '@dxos/schema';
19
23
  import { withLayout, withTheme } from '@dxos/storybook-utils';
20
24
 
21
25
  import { ChatContainer } from './ChatContainer';
22
- import { createChatThread } from './testing';
23
26
  import translations from '../translations';
24
27
 
25
28
  faker.seed(1);
@@ -28,25 +31,31 @@ const Story = () => {
28
31
  const client = useClient();
29
32
  const identity = useIdentity();
30
33
  const [space, setSpace] = useState<Space>();
31
- const [thread, setThread] = useState<ThreadType | null>();
34
+ const [channel, setChannel] = useState<ChannelType | null>();
32
35
 
33
36
  useEffect(() => {
34
37
  if (identity) {
35
38
  setTimeout(async () => {
36
39
  const space = await client.spaces.create();
37
- const thread = space.db.add(createChatThread(identity));
40
+ const channel = space.db.add(
41
+ create(ChannelType, {
42
+ queue: refFromDXN(new DXN(DXN.kind.QUEUE, [QueueSubspaceTags.DATA, space.id, ObjectId.random()])),
43
+ }),
44
+ );
38
45
  setSpace(space);
39
- setThread(thread);
46
+ setChannel(channel);
40
47
  });
41
48
  }
42
49
  }, [identity]);
43
50
 
44
- if (!identity || !thread) {
51
+ if (!identity || !channel || !space) {
45
52
  return null;
46
53
  }
47
54
 
48
55
  return (
49
- <main className='max-is-prose mli-auto bs-dvh overflow-hidden'>{space && <ChatContainer thread={thread} />}</main>
56
+ <main className='max-is-prose mli-auto bs-dvh overflow-hidden'>
57
+ <ChatContainer space={space} dxn={channel.queue.dxn} />
58
+ </main>
50
59
  );
51
60
  };
52
61
 
@@ -58,6 +67,7 @@ const meta: Meta = {
58
67
  render: () => <Story />,
59
68
  decorators: [
60
69
  withPluginManager({
70
+ plugins: [IntentPlugin()],
61
71
  capabilities: [
62
72
  contributes(
63
73
  Capabilities.ReactSurface,
@@ -4,16 +4,17 @@
4
4
 
5
5
  import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
6
6
 
7
- import { Ref } from '@dxos/echo-schema';
8
- import { create, makeRef, RefArray } from '@dxos/live-object';
9
- import { MessageType } from '@dxos/plugin-space/types';
10
- import { fullyQualifiedId, getSpace, useMembers } from '@dxos/react-client/echo';
7
+ import { createStatic } from '@dxos/echo-schema';
8
+ import { type DXN } from '@dxos/keys';
9
+ import { makeRef } from '@dxos/live-object';
10
+ import { type Space, useMembers, useQueue } from '@dxos/react-client/echo';
11
11
  import { useIdentity } from '@dxos/react-client/halo';
12
12
  import { Icon, ScrollArea, useThemeContext, useTranslation } from '@dxos/react-ui';
13
13
  import { createBasicExtensions, createThemeExtensions, listener } from '@dxos/react-ui-editor';
14
14
  import { StackItem } from '@dxos/react-ui-stack';
15
15
  import { mx } from '@dxos/react-ui-theme';
16
16
  import { MessageTextbox, type MessageTextboxProps, Thread, ThreadFooter, threadLayout } from '@dxos/react-ui-thread';
17
+ import { MessageType } from '@dxos/schema';
17
18
 
18
19
  import { MessageContainer } from './MessageContainer';
19
20
  import { command } from './command-extension';
@@ -34,11 +35,17 @@ export const ChatHeading = ({ attendableId }: { attendableId?: string }) => {
34
35
  );
35
36
  };
36
37
 
37
- export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: ThreadContainerProps) => {
38
+ export type ChatContainerProps = Omit<ThreadContainerProps, 'thread'> & {
39
+ space: Space;
40
+ dxn: DXN;
41
+ };
42
+
43
+ export const ChatContainer = ({ space, dxn, context, current, autoFocusTextbox }: ChatContainerProps) => {
44
+ const id = dxn.toString();
38
45
  const identity = useIdentity()!;
39
- const space = getSpace(thread);
40
46
  const members = useMembers(space?.key);
41
- const activity = useStatus(space, fullyQualifiedId(thread));
47
+ const queue = useQueue<MessageType>(dxn, { pollInterval: 1_000 });
48
+ const activity = useStatus(space, id);
42
49
  const { t } = useTranslation(THREAD_PLUGIN);
43
50
  // TODO(wittjosiah): This is a hack to reset the editor after a message is sent.
44
51
  const [_count, _setCount] = useState(0);
@@ -47,7 +54,7 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
47
54
  const threadScrollRef = useRef<HTMLDivElement | null>(null);
48
55
  const { themeMode } = useThemeContext();
49
56
 
50
- const textboxMetadata = getMessageMetadata(fullyQualifiedId(thread), identity);
57
+ const textboxMetadata = getMessageMetadata(id, identity);
51
58
  const messageRef = useRef('');
52
59
  const extensions = useMemo(
53
60
  () => [
@@ -72,20 +79,18 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
72
79
 
73
80
  // TODO(burdon): Change to model.
74
81
  const handleCreate: MessageTextboxProps['onSend'] = () => {
75
- if (!messageRef.current?.length) {
82
+ if (!messageRef.current?.length || !queue) {
76
83
  return false;
77
84
  }
78
85
 
79
- thread.messages.push(
80
- makeRef(
81
- create(MessageType, {
82
- sender: { identityKey: identity.identityKey.toHex() },
83
- timestamp: new Date().toISOString(),
84
- text: messageRef.current,
85
- context: context ? makeRef(context) : undefined,
86
- }),
87
- ),
88
- );
86
+ queue.append([
87
+ createStatic(MessageType, {
88
+ sender: { identityDid: identity.did },
89
+ created: new Date().toISOString(),
90
+ blocks: [{ type: 'text', text: messageRef.current }],
91
+ properties: context ? { context: makeRef(context) } : undefined,
92
+ }),
93
+ ]);
89
94
 
90
95
  messageRef.current = '';
91
96
  setAutoFocus(true);
@@ -94,24 +99,17 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
94
99
  return true;
95
100
  };
96
101
 
97
- const handleDelete = (id: string) => {
98
- const messageIndex = thread.messages.findIndex(Ref.hasObjectId(id));
99
- if (messageIndex !== -1) {
100
- thread.messages.splice(messageIndex, 1);
101
- }
102
- };
103
-
104
102
  return (
105
103
  <Thread
106
104
  current={current}
107
- id={fullyQualifiedId(thread)}
105
+ id={id}
108
106
  classNames='bs-full grid-rows-[1fr_min-content_min-content] overflow-hidden transition-[padding-block-end] [[data-sidebar-inline-start-state=open]_&]:lg:pbe-0'
109
107
  >
110
108
  <ScrollArea.Root classNames='col-span-2'>
111
109
  <ScrollArea.Viewport classNames='overflow-anchored after:overflow-anchor after:block after:bs-px after:-mbs-px [&>div]:min-bs-full [&>div]:!grid [&>div]:grid-rows-[1fr_0]'>
112
110
  <div role='none' className={mx(threadLayout, 'place-self-end')}>
113
- {RefArray.allResolvedTargets(thread.messages ?? []).map((message) => (
114
- <MessageContainer key={message.id} message={message} members={members} onDelete={handleDelete} />
111
+ {(queue?.items ?? []).map((message) => (
112
+ <MessageContainer key={message.id} message={message} members={members} />
115
113
  ))}
116
114
  </div>
117
115
  {/* NOTE(thure): This can’t also be the `overflow-anchor` because `ScrollArea` injects an interceding node that contains this necessary ref’d element. */}
@@ -126,3 +124,5 @@ export const ChatContainer = ({ thread, context, current, autoFocusTextbox }: Th
126
124
  </Thread>
127
125
  );
128
126
  };
127
+
128
+ export default ChatContainer;
@@ -179,8 +179,14 @@ export const CommentContainer = ({
179
179
  </div>
180
180
  </div>
181
181
  {/** TODO(dmaretskyi): How's `thread.messages` undefined? */}
182
- {RefArray.allResolvedTargets(thread.messages?.filter(isNonNullable) ?? []).map((message) => (
183
- <MessageContainer key={message.id} message={message} members={members} onDelete={handleMessageDelete} />
182
+ {RefArray.targets(thread.messages?.filter(isNonNullable) ?? []).map((message) => (
183
+ <MessageContainer
184
+ key={message.id}
185
+ editable
186
+ message={message}
187
+ members={members}
188
+ onDelete={handleMessageDelete}
189
+ />
184
190
  ))}
185
191
  {/*
186
192
  TODO(wittjosiah): Can't autofocus this generally.
@@ -9,11 +9,12 @@ import React, { useEffect, useState } from 'react';
9
9
 
10
10
  import { IntentPlugin } from '@dxos/app-framework';
11
11
  import { withPluginManager } from '@dxos/app-framework/testing';
12
- import { MessageType, ThreadType } from '@dxos/plugin-space/types';
12
+ import { ThreadType } from '@dxos/plugin-space/types';
13
13
  import { faker } from '@dxos/random';
14
14
  import { Filter, fullyQualifiedId, useQuery, useSpace } from '@dxos/react-client/echo';
15
15
  import { useIdentity } from '@dxos/react-client/halo';
16
16
  import { type ClientRepeatedComponentProps, ClientRepeater } from '@dxos/react-client/testing';
17
+ import { MessageType } from '@dxos/schema';
17
18
  import { withLayout, withTheme } from '@dxos/storybook-utils';
18
19
 
19
20
  import { CommentsContainer } from './CommentsContainer';