@agent-relay/dashboard 2.0.80 → 2.0.82

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 (244) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/{118-4c8241b0218335de.js → 118-ae2b650136a5a5fc.js} +1 -1
  3. package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +1 -0
  4. package/out/_next/static/chunks/app/app/[[...slug]]/{page-1e81c047cff17212.js → page-f7eca1b66fb4249b.js} +1 -1
  5. package/out/_next/static/chunks/app/{page-6892fe2dd07fb48b.js → page-0ee604f7070d14c0.js} +1 -1
  6. package/out/_next/static/css/8968d98ed4c4d33f.css +1 -0
  7. package/out/about.html +2 -2
  8. package/out/about.txt +1 -1
  9. package/out/app/onboarding.html +1 -1
  10. package/out/app/onboarding.txt +1 -1
  11. package/out/app.html +1 -1
  12. package/out/app.txt +2 -2
  13. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
  14. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  15. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  16. package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
  17. package/out/blog.html +2 -2
  18. package/out/blog.txt +1 -1
  19. package/out/careers.html +2 -2
  20. package/out/careers.txt +1 -1
  21. package/out/changelog.html +2 -2
  22. package/out/changelog.txt +1 -1
  23. package/out/cloud/link.html +1 -1
  24. package/out/cloud/link.txt +2 -2
  25. package/out/complete-profile.html +2 -2
  26. package/out/complete-profile.txt +1 -1
  27. package/out/connect-repos.html +1 -1
  28. package/out/connect-repos.txt +1 -1
  29. package/out/contact.html +2 -2
  30. package/out/contact.txt +1 -1
  31. package/out/docs.html +2 -2
  32. package/out/docs.txt +1 -1
  33. package/out/history.html +1 -1
  34. package/out/history.txt +2 -2
  35. package/out/index.html +1 -1
  36. package/out/index.txt +2 -2
  37. package/out/login.html +2 -2
  38. package/out/login.txt +1 -1
  39. package/out/metrics.html +1 -1
  40. package/out/metrics.txt +2 -2
  41. package/out/pricing.html +2 -2
  42. package/out/pricing.txt +1 -1
  43. package/out/privacy.html +2 -2
  44. package/out/privacy.txt +1 -1
  45. package/out/providers/setup/claude.html +1 -1
  46. package/out/providers/setup/claude.txt +1 -1
  47. package/out/providers/setup/codex.html +1 -1
  48. package/out/providers/setup/codex.txt +1 -1
  49. package/out/providers/setup/cursor.html +1 -1
  50. package/out/providers/setup/cursor.txt +1 -1
  51. package/out/providers.html +1 -1
  52. package/out/providers.txt +1 -1
  53. package/out/security.html +2 -2
  54. package/out/security.txt +1 -1
  55. package/out/signup.html +2 -2
  56. package/out/signup.txt +1 -1
  57. package/out/terms.html +2 -2
  58. package/out/terms.txt +1 -1
  59. package/package.json +7 -1
  60. package/src/app/about/page.tsx +7 -0
  61. package/src/app/app/[[...slug]]/DashboardPageClient.tsx +853 -0
  62. package/src/app/app/[[...slug]]/page.tsx +23 -0
  63. package/src/app/app/onboarding/page.tsx +394 -0
  64. package/src/app/apple-icon.png +0 -0
  65. package/src/app/blog/go-to-bed-wake-up-to-a-finished-product/page.tsx +88 -0
  66. package/src/app/blog/let-them-cook-multi-agent-orchestration/page.tsx +93 -0
  67. package/src/app/blog/page.tsx +15 -0
  68. package/src/app/careers/page.tsx +7 -0
  69. package/src/app/changelog/page.tsx +7 -0
  70. package/src/app/cloud/link/page.tsx +464 -0
  71. package/src/app/complete-profile/page.tsx +204 -0
  72. package/src/app/connect-repos/page.tsx +410 -0
  73. package/src/app/contact/page.tsx +7 -0
  74. package/src/app/docs/page.tsx +7 -0
  75. package/src/app/favicon.png +0 -0
  76. package/src/app/globals.css +200 -0
  77. package/src/app/history/page.tsx +658 -0
  78. package/src/app/layout.tsx +25 -0
  79. package/src/app/login/page.tsx +424 -0
  80. package/src/app/metrics/page.tsx +781 -0
  81. package/src/app/page.tsx +59 -0
  82. package/src/app/pricing/page.tsx +7 -0
  83. package/src/app/privacy/page.tsx +7 -0
  84. package/src/app/providers/page.tsx +193 -0
  85. package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +197 -0
  86. package/src/app/providers/setup/[provider]/constants.ts +35 -0
  87. package/src/app/providers/setup/[provider]/page.tsx +42 -0
  88. package/src/app/security/page.tsx +7 -0
  89. package/src/app/signup/page.tsx +533 -0
  90. package/src/app/terms/page.tsx +7 -0
  91. package/src/components/ActivityFeed.tsx +216 -0
  92. package/src/components/AddWorkspaceModal.tsx +170 -0
  93. package/src/components/AgentCard.test.tsx +134 -0
  94. package/src/components/AgentCard.tsx +585 -0
  95. package/src/components/AgentList.test.tsx +147 -0
  96. package/src/components/AgentList.tsx +419 -0
  97. package/src/components/AgentLogPreview.tsx +173 -0
  98. package/src/components/AgentProfilePanel.tsx +569 -0
  99. package/src/components/App.tsx +3424 -0
  100. package/src/components/BillingPanel.tsx +922 -0
  101. package/src/components/BillingResult.tsx +447 -0
  102. package/src/components/BroadcastComposer.tsx +690 -0
  103. package/src/components/ChannelAdminPanel.tsx +773 -0
  104. package/src/components/ChannelBrowser.tsx +385 -0
  105. package/src/components/ChannelChat.tsx +261 -0
  106. package/src/components/ChannelSidebar.tsx +399 -0
  107. package/src/components/CloudSessionProvider.tsx +130 -0
  108. package/src/components/CommandPalette.tsx +815 -0
  109. package/src/components/ConfirmationDialog.tsx +133 -0
  110. package/src/components/ConversationHistory.tsx +518 -0
  111. package/src/components/CoordinatorPanel.tsx +956 -0
  112. package/src/components/DecisionQueue.tsx +717 -0
  113. package/src/components/DirectMessageView.tsx +164 -0
  114. package/src/components/FileAutocomplete.tsx +368 -0
  115. package/src/components/FleetOverview.tsx +278 -0
  116. package/src/components/LogViewer.tsx +310 -0
  117. package/src/components/LogViewerPanel.tsx +482 -0
  118. package/src/components/Logo.tsx +284 -0
  119. package/src/components/MentionAutocomplete.tsx +384 -0
  120. package/src/components/MessageComposer.tsx +473 -0
  121. package/src/components/MessageList.tsx +725 -0
  122. package/src/components/MessageSenderName.tsx +91 -0
  123. package/src/components/MessageStatusIndicator.tsx +142 -0
  124. package/src/components/NewConversationModal.tsx +400 -0
  125. package/src/components/NotificationToast.tsx +488 -0
  126. package/src/components/OnlineUsersIndicator.tsx +164 -0
  127. package/src/components/Pagination.tsx +124 -0
  128. package/src/components/PricingPlans.tsx +386 -0
  129. package/src/components/ProjectList.tsx +711 -0
  130. package/src/components/ProviderAuthFlow.tsx +343 -0
  131. package/src/components/ProviderConnectionList.tsx +375 -0
  132. package/src/components/ProvisioningProgress.tsx +730 -0
  133. package/src/components/ReactionChips.tsx +70 -0
  134. package/src/components/ReactionPicker.tsx +121 -0
  135. package/src/components/RepoAccessPanel.tsx +787 -0
  136. package/src/components/RepositoriesPanel.tsx +901 -0
  137. package/src/components/ServerCard.tsx +202 -0
  138. package/src/components/SessionExpiredModal.tsx +128 -0
  139. package/src/components/SpawnModal.test.tsx +190 -0
  140. package/src/components/SpawnModal.tsx +1001 -0
  141. package/src/components/TaskAssignmentUI.tsx +375 -0
  142. package/src/components/TerminalProviderSetup.tsx +517 -0
  143. package/src/components/ThemeProvider.tsx +159 -0
  144. package/src/components/ThinkingIndicator.tsx +231 -0
  145. package/src/components/ThreadList.tsx +198 -0
  146. package/src/components/ThreadPanel.tsx +405 -0
  147. package/src/components/TrajectoryViewer.tsx +698 -0
  148. package/src/components/TypingIndicator.tsx +69 -0
  149. package/src/components/UsageBanner.tsx +231 -0
  150. package/src/components/UserProfilePanel.tsx +233 -0
  151. package/src/components/WorkspaceContext.tsx +95 -0
  152. package/src/components/WorkspaceSelector.tsx +234 -0
  153. package/src/components/WorkspaceStatusIndicator.tsx +396 -0
  154. package/src/components/XTermInteractive.tsx +516 -0
  155. package/src/components/XTermLogViewer.tsx +719 -0
  156. package/src/components/channels/ChannelDialogs.tsx +1411 -0
  157. package/src/components/channels/ChannelHeader.tsx +317 -0
  158. package/src/components/channels/ChannelMessageList.tsx +463 -0
  159. package/src/components/channels/ChannelViewV1.tsx +146 -0
  160. package/src/components/channels/MessageInput.tsx +302 -0
  161. package/src/components/channels/SearchInput.tsx +172 -0
  162. package/src/components/channels/SearchResults.tsx +336 -0
  163. package/src/components/channels/api.test.ts +1527 -0
  164. package/src/components/channels/api.ts +703 -0
  165. package/src/components/channels/index.ts +76 -0
  166. package/src/components/channels/mockApi.ts +344 -0
  167. package/src/components/channels/types.ts +566 -0
  168. package/src/components/hooks/index.ts +58 -0
  169. package/src/components/hooks/useAgentLogs.ts +504 -0
  170. package/src/components/hooks/useAgents.ts +127 -0
  171. package/src/components/hooks/useBroadcastDedup.test.ts +371 -0
  172. package/src/components/hooks/useBroadcastDedup.ts +86 -0
  173. package/src/components/hooks/useChannelAdmin.ts +329 -0
  174. package/src/components/hooks/useChannelBrowser.ts +239 -0
  175. package/src/components/hooks/useChannelCommands.ts +138 -0
  176. package/src/components/hooks/useChannels.ts +367 -0
  177. package/src/components/hooks/useDebounce.ts +29 -0
  178. package/src/components/hooks/useDirectMessage.test.ts +952 -0
  179. package/src/components/hooks/useDirectMessage.ts +141 -0
  180. package/src/components/hooks/useMessages.ts +310 -0
  181. package/src/components/hooks/useOrchestrator.test.ts +165 -0
  182. package/src/components/hooks/useOrchestrator.ts +424 -0
  183. package/src/components/hooks/usePinnedAgents.test.ts +356 -0
  184. package/src/components/hooks/usePinnedAgents.ts +140 -0
  185. package/src/components/hooks/usePresence.test.ts +245 -0
  186. package/src/components/hooks/usePresence.ts +377 -0
  187. package/src/components/hooks/useRecentRepos.ts +130 -0
  188. package/src/components/hooks/useSession.ts +209 -0
  189. package/src/components/hooks/useThread.ts +138 -0
  190. package/src/components/hooks/useTrajectory.ts +265 -0
  191. package/src/components/hooks/useWebSocket.ts +290 -0
  192. package/src/components/hooks/useWorkspaceMembers.ts +132 -0
  193. package/src/components/hooks/useWorkspaceRepos.ts +73 -0
  194. package/src/components/hooks/useWorkspaceStatus.ts +237 -0
  195. package/src/components/index.ts +81 -0
  196. package/src/components/layout/Header.tsx +311 -0
  197. package/src/components/layout/RepoContextHeader.tsx +361 -0
  198. package/src/components/layout/Sidebar.archive.test.tsx +126 -0
  199. package/src/components/layout/Sidebar.test.tsx +691 -0
  200. package/src/components/layout/Sidebar.tsx +900 -0
  201. package/src/components/layout/index.ts +7 -0
  202. package/src/components/settings/BillingSettingsPanel.tsx +564 -0
  203. package/src/components/settings/SettingsPage.tsx +683 -0
  204. package/src/components/settings/TeamSettingsPanel.tsx +560 -0
  205. package/src/components/settings/WorkspaceSettingsPanel.tsx +1368 -0
  206. package/src/components/settings/index.ts +11 -0
  207. package/src/components/settings/types.ts +79 -0
  208. package/src/components/utils/messageFormatting.test.tsx +331 -0
  209. package/src/components/utils/messageFormatting.tsx +597 -0
  210. package/src/index.ts +63 -0
  211. package/src/landing/AboutPage.tsx +77 -0
  212. package/src/landing/BlogContent.tsx +187 -0
  213. package/src/landing/BlogPage.tsx +47 -0
  214. package/src/landing/CareersPage.tsx +53 -0
  215. package/src/landing/ChangelogPage.tsx +33 -0
  216. package/src/landing/ContactPage.tsx +41 -0
  217. package/src/landing/DocsPage.tsx +43 -0
  218. package/src/landing/LandingPage.tsx +702 -0
  219. package/src/landing/PricingPage.tsx +549 -0
  220. package/src/landing/PrivacyPage.tsx +117 -0
  221. package/src/landing/SecurityPage.tsx +42 -0
  222. package/src/landing/StaticPage.tsx +165 -0
  223. package/src/landing/TermsPage.tsx +125 -0
  224. package/src/landing/blogData.ts +312 -0
  225. package/src/landing/index.ts +18 -0
  226. package/src/landing/styles.css +3673 -0
  227. package/src/lib/agent-merge.test.ts +43 -0
  228. package/src/lib/agent-merge.ts +35 -0
  229. package/src/lib/api.ts +1294 -0
  230. package/src/lib/cloudApi.ts +893 -0
  231. package/src/lib/colors.test.ts +175 -0
  232. package/src/lib/colors.ts +218 -0
  233. package/src/lib/config.ts +109 -0
  234. package/src/lib/hierarchy.ts +242 -0
  235. package/src/lib/stuckDetection.ts +142 -0
  236. package/src/lib/useUrlRouting.ts +190 -0
  237. package/src/types/index.ts +317 -0
  238. package/src/types/threading.ts +7 -0
  239. package/out/_next/static/chunks/285-dc644487a8d6500d.js +0 -1
  240. package/out/_next/static/css/4c58d9cf493aa626.css +0 -1
  241. /package/out/_next/static/{AqelRhy1vr2nBUcU0Iqcp → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
  242. /package/out/_next/static/{AqelRhy1vr2nBUcU0Iqcp → IxfA6RZu4trcsEMYlkQra}/_ssgManifest.js +0 -0
  243. /package/out/_next/static/chunks/{528-d375bc8b46912d2c.js → 528-f5f676996d613c25.js} +0 -0
  244. /package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/{page-a58308f43557b908.js → page-b194f207fbd91862.js} +0 -0
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Channels V1 Component Library
3
+ *
4
+ * Comprehensive channel-based messaging UI components.
5
+ * Built for the Agent Relay dashboard.
6
+ */
7
+
8
+ // Types
9
+ export * from './types';
10
+
11
+ // API (real + mock fallback)
12
+ export {
13
+ listChannels,
14
+ getChannel,
15
+ getMessages,
16
+ createChannel,
17
+ sendMessage,
18
+ joinChannel,
19
+ leaveChannel,
20
+ archiveChannel,
21
+ unarchiveChannel,
22
+ deleteChannel,
23
+ markRead,
24
+ pinMessage,
25
+ unpinMessage,
26
+ getMentionSuggestions,
27
+ searchMessages,
28
+ searchChannel,
29
+ // Admin API (Task 10)
30
+ updateChannel,
31
+ addMember,
32
+ removeMember,
33
+ updateMemberRole,
34
+ getChannelMembers,
35
+ isRealApiEnabled,
36
+ setApiMode,
37
+ getApiMode,
38
+ ApiError,
39
+ } from './api';
40
+
41
+ // Components
42
+ export { ChannelHeader } from './ChannelHeader';
43
+ export { ChannelMessageList } from './ChannelMessageList';
44
+ export { MessageInput } from './MessageInput';
45
+ export { ChannelViewV1 } from './ChannelViewV1';
46
+ export { SearchInput } from './SearchInput';
47
+ export { SearchResults } from './SearchResults';
48
+
49
+ // Dialogs
50
+ export {
51
+ ArchiveChannelDialog,
52
+ DeleteChannelDialog,
53
+ LeaveChannelDialog,
54
+ CreateChannelModal,
55
+ InviteToChannelModal,
56
+ // Admin Tools (Task 10)
57
+ ChannelSettingsModal,
58
+ MemberManagementPanel,
59
+ } from './ChannelDialogs';
60
+
61
+ // Re-export prop types for convenience
62
+ export type { ChannelHeaderProps } from './types';
63
+ export type { ChannelMessageListProps } from './types';
64
+ export type { MessageInputProps } from './types';
65
+ export type { ChannelViewV1Props } from './ChannelViewV1';
66
+ export type { SearchInputProps, SearchResultsProps, SearchResult, SearchResponse } from './types';
67
+ export type {
68
+ ArchiveChannelDialogProps,
69
+ DeleteChannelDialogProps,
70
+ LeaveChannelDialogProps,
71
+ CreateChannelModalProps,
72
+ InviteToChannelModalProps,
73
+ // Admin Tools (Task 10)
74
+ ChannelSettingsModalProps,
75
+ MemberManagementPanelProps,
76
+ } from './ChannelDialogs';
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Mock API Service for Channels V1
3
+ *
4
+ * Provides mock implementations of the channel APIs for development.
5
+ * Replace with real API calls when backend is ready.
6
+ */
7
+
8
+ import type {
9
+ Channel,
10
+ ChannelMember,
11
+ ChannelMessage,
12
+ ListChannelsResponse,
13
+ GetChannelResponse,
14
+ GetMessagesResponse,
15
+ CreateChannelRequest,
16
+ CreateChannelResponse,
17
+ SendMessageRequest,
18
+ SendMessageResponse,
19
+ } from './types';
20
+
21
+ import {
22
+ MOCK_CHANNELS,
23
+ MOCK_ARCHIVED_CHANNELS,
24
+ MOCK_MESSAGES,
25
+ } from './types';
26
+
27
+ // Only seed demo data when explicitly enabled (prevents example channels in cloud)
28
+ const ENABLE_DEMO_DATA = process.env.NEXT_PUBLIC_ENABLE_CHANNEL_DEMO === 'true';
29
+
30
+ function getInitialChannels(): Channel[] {
31
+ return ENABLE_DEMO_DATA ? [...MOCK_CHANNELS] : [];
32
+ }
33
+
34
+ function getInitialArchivedChannels(): Channel[] {
35
+ return ENABLE_DEMO_DATA ? [...MOCK_ARCHIVED_CHANNELS] : [];
36
+ }
37
+
38
+ function getInitialMessages(): ChannelMessage[] {
39
+ return ENABLE_DEMO_DATA ? [...MOCK_MESSAGES] : [];
40
+ }
41
+
42
+ // Simulated latency for realistic UX
43
+ const MOCK_LATENCY_MS = 300;
44
+
45
+ // In-memory state
46
+ let channels = getInitialChannels();
47
+ let archivedChannels = getInitialArchivedChannels();
48
+ let messages = getInitialMessages();
49
+ let messageIdCounter = ENABLE_DEMO_DATA ? 100 : 0;
50
+
51
+ /**
52
+ * Simulate network latency
53
+ */
54
+ async function delay(ms: number = MOCK_LATENCY_MS): Promise<void> {
55
+ return new Promise(resolve => setTimeout(resolve, ms));
56
+ }
57
+
58
+ /**
59
+ * Generate a unique message ID
60
+ */
61
+ function generateMessageId(): string {
62
+ return `msg-${++messageIdCounter}`;
63
+ }
64
+
65
+ /**
66
+ * Mock: List channels for current user
67
+ */
68
+ export async function listChannels(): Promise<ListChannelsResponse> {
69
+ await delay();
70
+ return {
71
+ channels: [...channels],
72
+ archivedChannels: [...archivedChannels],
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Mock: Get channel details
78
+ */
79
+ export async function getChannel(channelId: string): Promise<GetChannelResponse> {
80
+ await delay();
81
+
82
+ const channel = [...channels, ...archivedChannels].find(c => c.id === channelId);
83
+ if (!channel) {
84
+ throw new Error(`Channel not found: ${channelId}`);
85
+ }
86
+
87
+ // Generate mock members
88
+ const mockMembers: ChannelMember[] = [
89
+ {
90
+ id: 'Lead',
91
+ displayName: 'Lead Agent',
92
+ entityType: 'agent',
93
+ role: 'owner',
94
+ status: 'online',
95
+ joinedAt: channel.createdAt,
96
+ },
97
+ {
98
+ id: 'Frontend',
99
+ displayName: 'Frontend Engineer',
100
+ entityType: 'agent',
101
+ role: 'member',
102
+ status: 'online',
103
+ joinedAt: channel.createdAt,
104
+ },
105
+ {
106
+ id: 'CodeReviewer',
107
+ displayName: 'Code Reviewer',
108
+ entityType: 'agent',
109
+ role: 'member',
110
+ status: 'away',
111
+ joinedAt: channel.createdAt,
112
+ },
113
+ ];
114
+
115
+ return {
116
+ channel,
117
+ members: mockMembers,
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Mock: Get messages in a channel
123
+ */
124
+ export async function getMessages(
125
+ channelId: string,
126
+ options?: { before?: string; limit?: number; threadId?: string }
127
+ ): Promise<GetMessagesResponse> {
128
+ await delay();
129
+
130
+ const limit = options?.limit || 50;
131
+ let channelMessages = messages.filter(m => m.channelId === channelId);
132
+
133
+ if (options?.threadId) {
134
+ channelMessages = channelMessages.filter(m =>
135
+ m.threadId === options.threadId || m.id === options.threadId
136
+ );
137
+ }
138
+
139
+ if (options?.before) {
140
+ const beforeIndex = channelMessages.findIndex(m => m.id === options.before);
141
+ if (beforeIndex > 0) {
142
+ channelMessages = channelMessages.slice(0, beforeIndex);
143
+ }
144
+ }
145
+
146
+ const result = channelMessages.slice(-limit);
147
+
148
+ // Calculate unread state (mock: last 2 messages are unread)
149
+ const unreadCount = Math.min(2, result.filter(m => !m.isRead).length);
150
+ const firstUnread = result.find(m => !m.isRead);
151
+
152
+ return {
153
+ messages: result,
154
+ hasMore: channelMessages.length > limit,
155
+ unread: {
156
+ count: unreadCount,
157
+ firstUnreadMessageId: firstUnread?.id,
158
+ },
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Mock: Create a channel
164
+ */
165
+ export async function createChannel(
166
+ request: CreateChannelRequest
167
+ ): Promise<CreateChannelResponse> {
168
+ await delay();
169
+
170
+ const newChannel: Channel = {
171
+ id: `#${request.name}`,
172
+ name: request.name,
173
+ description: request.description,
174
+ visibility: request.visibility,
175
+ status: 'active',
176
+ createdAt: new Date().toISOString(),
177
+ createdBy: 'CurrentUser',
178
+ memberCount: 1,
179
+ unreadCount: 0,
180
+ hasMentions: false,
181
+ isDm: false,
182
+ };
183
+
184
+ channels.push(newChannel);
185
+
186
+ return { channel: newChannel };
187
+ }
188
+
189
+ /**
190
+ * Mock: Send a message
191
+ */
192
+ export async function sendMessage(
193
+ channelId: string,
194
+ request: SendMessageRequest
195
+ ): Promise<SendMessageResponse> {
196
+ await delay(100); // Fast for good UX
197
+
198
+ const newMessage: ChannelMessage = {
199
+ id: generateMessageId(),
200
+ channelId,
201
+ from: 'CurrentUser',
202
+ fromEntityType: 'user',
203
+ content: request.content,
204
+ timestamp: new Date().toISOString(),
205
+ threadId: request.threadId,
206
+ isRead: true,
207
+ };
208
+
209
+ messages.push(newMessage);
210
+
211
+ // Update channel's last activity
212
+ const channel = channels.find(c => c.id === channelId);
213
+ if (channel) {
214
+ channel.lastActivityAt = newMessage.timestamp;
215
+ channel.lastMessage = {
216
+ content: request.content.slice(0, 100),
217
+ from: 'CurrentUser',
218
+ timestamp: newMessage.timestamp,
219
+ };
220
+ }
221
+
222
+ return { message: newMessage };
223
+ }
224
+
225
+ /**
226
+ * Mock: Join a channel
227
+ */
228
+ export async function joinChannel(channelId: string): Promise<Channel> {
229
+ await delay();
230
+
231
+ const channel = channels.find(c => c.id === channelId);
232
+ if (!channel) {
233
+ throw new Error(`Channel not found: ${channelId}`);
234
+ }
235
+
236
+ channel.memberCount++;
237
+ return channel;
238
+ }
239
+
240
+ /**
241
+ * Mock: Leave a channel
242
+ */
243
+ export async function leaveChannel(channelId: string): Promise<void> {
244
+ await delay();
245
+
246
+ const channelIndex = channels.findIndex(c => c.id === channelId);
247
+ if (channelIndex >= 0) {
248
+ channels[channelIndex].memberCount--;
249
+ // Optionally remove from list if member count is personal preference
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Mock: Archive a channel
255
+ */
256
+ export async function archiveChannel(channelId: string): Promise<Channel> {
257
+ await delay();
258
+
259
+ const channelIndex = channels.findIndex(c => c.id === channelId);
260
+ if (channelIndex < 0) {
261
+ throw new Error(`Channel not found: ${channelId}`);
262
+ }
263
+
264
+ const channel = channels[channelIndex];
265
+ channel.status = 'archived';
266
+ channels.splice(channelIndex, 1);
267
+ archivedChannels.push(channel);
268
+
269
+ return channel;
270
+ }
271
+
272
+ /**
273
+ * Mock: Unarchive a channel
274
+ */
275
+ export async function unarchiveChannel(channelId: string): Promise<Channel> {
276
+ await delay();
277
+
278
+ const channelIndex = archivedChannels.findIndex(c => c.id === channelId);
279
+ if (channelIndex < 0) {
280
+ throw new Error(`Channel not found: ${channelId}`);
281
+ }
282
+
283
+ const channel = archivedChannels[channelIndex];
284
+ channel.status = 'active';
285
+ archivedChannels.splice(channelIndex, 1);
286
+ channels.push(channel);
287
+
288
+ return channel;
289
+ }
290
+
291
+ /**
292
+ * Mock: Delete a channel
293
+ */
294
+ export async function deleteChannel(channelId: string): Promise<void> {
295
+ await delay();
296
+
297
+ channels = channels.filter(c => c.id !== channelId);
298
+ archivedChannels = archivedChannels.filter(c => c.id !== channelId);
299
+ messages = messages.filter(m => m.channelId !== channelId);
300
+ }
301
+
302
+ /**
303
+ * Mock: Mark messages as read
304
+ */
305
+ export async function markRead(
306
+ channelId: string,
307
+ upToTimestamp: string
308
+ ): Promise<void> {
309
+ await delay(50);
310
+
311
+ const channelMessages = messages.filter(m => m.channelId === channelId);
312
+ const upToTime = new Date(upToTimestamp).getTime();
313
+
314
+ channelMessages.forEach(m => {
315
+ if (new Date(m.timestamp).getTime() <= upToTime) {
316
+ m.isRead = true;
317
+ }
318
+ });
319
+
320
+ // Update channel unread count
321
+ const channel = channels.find(c => c.id === channelId);
322
+ if (channel) {
323
+ channel.unreadCount = channelMessages.filter(m => !m.isRead).length;
324
+ channel.hasMentions = false;
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Mock: Get mention suggestions
330
+ */
331
+ export async function getMentionSuggestions(): Promise<string[]> {
332
+ await delay(50);
333
+ return ['Lead', 'Frontend', 'CodeReviewer', 'Backend', 'DevOps', 'QA'];
334
+ }
335
+
336
+ /**
337
+ * Reset mock state (for testing)
338
+ */
339
+ export function resetMockState(): void {
340
+ channels = getInitialChannels();
341
+ archivedChannels = getInitialArchivedChannels();
342
+ messages = getInitialMessages();
343
+ messageIdCounter = ENABLE_DEMO_DATA ? 100 : 0;
344
+ }