@natoe/colab 0.1.0 → 0.1.2

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.
package/dist/index.js CHANGED
@@ -1,9 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
3
+ var React4 = require('react');
4
4
  var phoenix = require('phoenix');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React4__default = /*#__PURE__*/_interopDefault(React4);
10
+
7
11
  // src/components/CollabPanel.tsx
8
12
 
9
13
  // src/core/constants.ts
@@ -316,25 +320,25 @@ var CollabSocket = class {
316
320
  this.config = null;
317
321
  }
318
322
  };
319
- var CollabContext = react.createContext(null);
323
+ var CollabContext = React4.createContext(null);
320
324
  function useCollab() {
321
- const context = react.useContext(CollabContext);
325
+ const context = React4.useContext(CollabContext);
322
326
  if (!context) {
323
327
  throw new Error("useCollab must be used within a <CollabProvider>");
324
328
  }
325
329
  return context;
326
330
  }
327
331
  function CollabProvider({ config, apiBaseUrl, children }) {
328
- const [socket, setSocket] = react.useState(() => {
332
+ const [socket, setSocket] = React4.useState(() => {
329
333
  if (typeof window === "undefined") return null;
330
334
  return new CollabSocket();
331
335
  });
332
- const [unreadCounts, setUnreadCounts] = react.useState({});
333
- const pendingOrderIds = react.useRef(/* @__PURE__ */ new Set());
334
- const pendingResolvers = react.useRef(/* @__PURE__ */ new Map());
335
- const previewCache = react.useRef(/* @__PURE__ */ new Map());
336
- const batchScheduled = react.useRef(false);
337
- react.useEffect(() => {
336
+ const [unreadCounts, setUnreadCounts] = React4.useState({});
337
+ const pendingOrderIds = React4.useRef(/* @__PURE__ */ new Set());
338
+ const pendingResolvers = React4.useRef(/* @__PURE__ */ new Map());
339
+ const previewCache = React4.useRef(/* @__PURE__ */ new Map());
340
+ const batchScheduled = React4.useRef(false);
341
+ React4.useEffect(() => {
338
342
  if (typeof window === "undefined") return;
339
343
  let s = socket;
340
344
  if (!s) {
@@ -349,14 +353,14 @@ function CollabProvider({ config, apiBaseUrl, children }) {
349
353
  s.connect(config);
350
354
  }
351
355
  }, [config.socketUrl, config.userId]);
352
- const authHeaders = react.useCallback(
356
+ const authHeaders = React4.useCallback(
353
357
  () => ({
354
358
  Authorization: `Bearer ${config.getToken()}`,
355
359
  "Content-Type": "application/json"
356
360
  }),
357
361
  [config]
358
362
  );
359
- const flushPreviewBatch = react.useCallback(async () => {
363
+ const flushPreviewBatch = React4.useCallback(async () => {
360
364
  const orderIds = Array.from(pendingOrderIds.current);
361
365
  const resolvers = new Map(pendingResolvers.current);
362
366
  pendingOrderIds.current.clear();
@@ -386,7 +390,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
386
390
  });
387
391
  }
388
392
  }, [apiBaseUrl, authHeaders, config]);
389
- const requestPreview = react.useCallback(
393
+ const requestPreview = React4.useCallback(
390
394
  (orderId) => {
391
395
  if (previewCache.current.has(orderId)) {
392
396
  return Promise.resolve(previewCache.current.get(orderId) ?? null);
@@ -404,10 +408,10 @@ function CollabProvider({ config, apiBaseUrl, children }) {
404
408
  },
405
409
  [flushPreviewBatch]
406
410
  );
407
- const invalidatePreview = react.useCallback((orderId) => {
411
+ const invalidatePreview = React4.useCallback((orderId) => {
408
412
  previewCache.current.delete(orderId);
409
413
  }, []);
410
- const fetchMessages = react.useCallback(
414
+ const fetchMessages = React4.useCallback(
411
415
  async (conversationId, options = {}) => {
412
416
  const limit = options.limit ?? MESSAGES_PAGE_SIZE;
413
417
  const params = new URLSearchParams({ limit: String(limit) });
@@ -423,7 +427,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
423
427
  },
424
428
  [apiBaseUrl, authHeaders]
425
429
  );
426
- const createConversationWithMessage = react.useCallback(
430
+ const createConversationWithMessage = React4.useCallback(
427
431
  async (orderId, message, options) => {
428
432
  if (!orderId || !orderId.trim()) {
429
433
  throw {
@@ -452,7 +456,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
452
456
  },
453
457
  [apiBaseUrl, authHeaders]
454
458
  );
455
- const addParticipant = react.useCallback(
459
+ const addParticipant = React4.useCallback(
456
460
  async (conversationId, userId) => {
457
461
  const response = await fetch(
458
462
  `${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/participants`,
@@ -468,7 +472,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
468
472
  },
469
473
  [apiBaseUrl, authHeaders]
470
474
  );
471
- const removeParticipant = react.useCallback(
475
+ const removeParticipant = React4.useCallback(
472
476
  async (conversationId, userId) => {
473
477
  const response = await fetch(
474
478
  `${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/participants/${userId}`,
@@ -483,7 +487,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
483
487
  },
484
488
  [apiBaseUrl, authHeaders]
485
489
  );
486
- const fetchConversationList = react.useCallback(async () => {
490
+ const fetchConversationList = React4.useCallback(async () => {
487
491
  const response = await fetch(`${apiBaseUrl}/api/natoe-colab/conversations`, {
488
492
  headers: authHeaders()
489
493
  });
@@ -492,7 +496,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
492
496
  }
493
497
  return snakeToCamel(await response.json());
494
498
  }, [apiBaseUrl, authHeaders]);
495
- const pinMessage = react.useCallback(
499
+ const pinMessage = React4.useCallback(
496
500
  async (conversationId, messageId) => {
497
501
  const response = await fetch(
498
502
  `${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/messages/${messageId}/pin`,
@@ -508,7 +512,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
508
512
  },
509
513
  [apiBaseUrl, authHeaders]
510
514
  );
511
- const unpinMessage = react.useCallback(
515
+ const unpinMessage = React4.useCallback(
512
516
  async (conversationId, messageId) => {
513
517
  const response = await fetch(
514
518
  `${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/messages/${messageId}/pin`,
@@ -523,7 +527,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
523
527
  },
524
528
  [apiBaseUrl, authHeaders]
525
529
  );
526
- const fetchPinnedMessages = react.useCallback(
530
+ const fetchPinnedMessages = React4.useCallback(
527
531
  async (conversationId) => {
528
532
  const response = await fetch(
529
533
  `${apiBaseUrl}/api/natoe-colab/conversations/${conversationId}/pinned`,
@@ -587,25 +591,25 @@ function useConversation({
587
591
  pinMessage: pinMessageApi,
588
592
  unpinMessage: unpinMessageApi
589
593
  } = useCollab();
590
- const [conversation, setConversation] = react.useState(null);
591
- const [messages, setMessages] = react.useState([]);
592
- const [pinnedMessages, setPinnedMessages] = react.useState([]);
593
- const [participants, setParticipants] = react.useState([]);
594
- const [typingUsers, setTypingUsers] = react.useState([]);
595
- const [isLoading, setIsLoading] = react.useState(true);
596
- const [hasMore, setHasMore] = react.useState(true);
597
- const [error, setError] = react.useState(null);
598
- const [isConnected, setIsConnected] = react.useState(false);
599
- const [replyTo, setReplyTo] = react.useState(null);
600
- const joinedConversationId = react.useRef(null);
601
- const typingTimers = react.useRef(/* @__PURE__ */ new Map());
602
- const ensureConversationInFlight = react.useRef(null);
603
- const markedReadIdsRef = react.useRef(/* @__PURE__ */ new Set());
604
- const buildName = react.useCallback(
594
+ const [conversation, setConversation] = React4.useState(null);
595
+ const [messages, setMessages] = React4.useState([]);
596
+ const [pinnedMessages, setPinnedMessages] = React4.useState([]);
597
+ const [participants, setParticipants] = React4.useState([]);
598
+ const [typingUsers, setTypingUsers] = React4.useState([]);
599
+ const [isLoading, setIsLoading] = React4.useState(true);
600
+ const [hasMore, setHasMore] = React4.useState(true);
601
+ const [error, setError] = React4.useState(null);
602
+ const [isConnected, setIsConnected] = React4.useState(false);
603
+ const [replyTo, setReplyTo] = React4.useState(null);
604
+ const joinedConversationId = React4.useRef(null);
605
+ const typingTimers = React4.useRef(/* @__PURE__ */ new Map());
606
+ const ensureConversationInFlight = React4.useRef(null);
607
+ const markedReadIdsRef = React4.useRef(/* @__PURE__ */ new Set());
608
+ const buildName = React4.useCallback(
605
609
  () => buildChannelName(patientData),
606
610
  [patientData]
607
611
  );
608
- const joinChannel = react.useCallback(
612
+ const joinChannel = React4.useCallback(
609
613
  (conv) => {
610
614
  if (joinedConversationId.current === conv.id) return;
611
615
  socket.joinConversation(conv.id, {
@@ -688,7 +692,7 @@ function useConversation({
688
692
  },
689
693
  [socket, config.userId]
690
694
  );
691
- react.useEffect(() => {
695
+ React4.useEffect(() => {
692
696
  let cancelled = false;
693
697
  const init = async () => {
694
698
  setIsLoading(true);
@@ -757,7 +761,7 @@ function useConversation({
757
761
  setIsConnected(false);
758
762
  };
759
763
  }, [orderId]);
760
- const ensureConversation = react.useCallback(
764
+ const ensureConversation = React4.useCallback(
761
765
  async (payload) => {
762
766
  if (conversation) return { conv: conversation, persistedByCreate: false };
763
767
  if (ensureConversationInFlight.current) {
@@ -811,7 +815,7 @@ function useConversation({
811
815
  fetchMessages
812
816
  ]
813
817
  );
814
- const sendMessage = react.useCallback(
818
+ const sendMessage = React4.useCallback(
815
819
  async (body) => {
816
820
  const payload = {
817
821
  body,
@@ -855,7 +859,7 @@ function useConversation({
855
859
  },
856
860
  [ensureConversation, socket, replyTo, config]
857
861
  );
858
- const sendAudioMessage = react.useCallback(
862
+ const sendAudioMessage = React4.useCallback(
859
863
  async (audioBlob, duration) => {
860
864
  if (!config.onUploadFile) return;
861
865
  const url = await config.onUploadFile(audioBlob, "voice-message.webm");
@@ -871,7 +875,7 @@ function useConversation({
871
875
  },
872
876
  [config, ensureConversation, socket]
873
877
  );
874
- const sendFileMessage = react.useCallback(
878
+ const sendFileMessage = React4.useCallback(
875
879
  async (file) => {
876
880
  if (!config.onUploadFile) return;
877
881
  const url = await config.onUploadFile(file, file.name);
@@ -888,7 +892,7 @@ function useConversation({
888
892
  },
889
893
  [config, ensureConversation, socket]
890
894
  );
891
- const sendDeepLink = react.useCallback(
895
+ const sendDeepLink = React4.useCallback(
892
896
  async (path, label) => {
893
897
  const payload = {
894
898
  body: label,
@@ -901,14 +905,14 @@ function useConversation({
901
905
  },
902
906
  [ensureConversation, socket]
903
907
  );
904
- const sendTypingIndicator = react.useCallback(
908
+ const sendTypingIndicator = React4.useCallback(
905
909
  (isTyping) => {
906
910
  if (!conversation || !isConnected) return;
907
911
  socket.sendTyping(conversation.id, isTyping);
908
912
  },
909
913
  [conversation, isConnected, socket]
910
914
  );
911
- const markAsRead = react.useCallback(
915
+ const markAsRead = React4.useCallback(
912
916
  (messageId) => {
913
917
  if (!conversation || !isConnected) return;
914
918
  if (markedReadIdsRef.current.has(messageId)) return;
@@ -917,7 +921,7 @@ function useConversation({
917
921
  },
918
922
  [conversation, isConnected, socket]
919
923
  );
920
- const loadMoreMessages = react.useCallback(async () => {
924
+ const loadMoreMessages = React4.useCallback(async () => {
921
925
  if (!conversation || !hasMore || isLoading) return;
922
926
  const oldestMessage = messages[0];
923
927
  const before = oldestMessage?.id;
@@ -936,7 +940,7 @@ function useConversation({
936
940
  setIsLoading(false);
937
941
  }
938
942
  }, [conversation, hasMore, isLoading, messages, fetchMessages, config]);
939
- const pinMessage = react.useCallback(
943
+ const pinMessage = React4.useCallback(
940
944
  async (messageId) => {
941
945
  if (!conversation) return;
942
946
  try {
@@ -951,7 +955,7 @@ function useConversation({
951
955
  },
952
956
  [conversation, pinMessageApi, config]
953
957
  );
954
- const unpinMessage = react.useCallback(
958
+ const unpinMessage = React4.useCallback(
955
959
  async (messageId) => {
956
960
  if (!conversation) return;
957
961
  try {
@@ -991,10 +995,10 @@ function useConversation({
991
995
  }
992
996
  function useChannelSettings({ conversationId }) {
993
997
  const { socket, config } = useCollab();
994
- const [isUpdating, setIsUpdating] = react.useState(false);
995
- const [error, setError] = react.useState(null);
998
+ const [isUpdating, setIsUpdating] = React4.useState(false);
999
+ const [error, setError] = React4.useState(null);
996
1000
  const isAdmin = config.userRole === "admin";
997
- const withLoading = react.useCallback(
1001
+ const withLoading = React4.useCallback(
998
1002
  async (operation) => {
999
1003
  if (!conversationId) return;
1000
1004
  setIsUpdating(true);
@@ -1013,25 +1017,25 @@ function useChannelSettings({ conversationId }) {
1013
1017
  },
1014
1018
  [conversationId, config]
1015
1019
  );
1016
- const inviteUser = react.useCallback(
1020
+ const inviteUser = React4.useCallback(
1017
1021
  async (payload) => {
1018
1022
  await withLoading(() => socket.inviteUser(conversationId, payload));
1019
1023
  },
1020
1024
  [conversationId, socket, withLoading]
1021
1025
  );
1022
- const removeUser = react.useCallback(
1026
+ const removeUser = React4.useCallback(
1023
1027
  async (userId) => {
1024
1028
  await withLoading(() => socket.removeUser(conversationId, userId));
1025
1029
  },
1026
1030
  [conversationId, socket, withLoading]
1027
1031
  );
1028
- const leaveChannel = react.useCallback(async () => {
1032
+ const leaveChannel = React4.useCallback(async () => {
1029
1033
  await withLoading(async () => {
1030
1034
  await socket.removeUser(conversationId, config.userId);
1031
1035
  socket.leaveConversation(conversationId);
1032
1036
  });
1033
1037
  }, [conversationId, socket, config.userId, withLoading]);
1034
- const deleteChannel = react.useCallback(async () => {
1038
+ const deleteChannel = React4.useCallback(async () => {
1035
1039
  if (!isAdmin) {
1036
1040
  const err = {
1037
1041
  code: "FORBIDDEN",
@@ -1043,7 +1047,7 @@ function useChannelSettings({ conversationId }) {
1043
1047
  }
1044
1048
  await withLoading(() => socket.deleteChannel(conversationId));
1045
1049
  }, [conversationId, socket, isAdmin, config, withLoading]);
1046
- const updateChannel = react.useCallback(
1050
+ const updateChannel = React4.useCallback(
1047
1051
  async (payload) => {
1048
1052
  await withLoading(() => socket.updateChannel(conversationId, payload));
1049
1053
  },
@@ -1062,7 +1066,7 @@ function useChannelSettings({ conversationId }) {
1062
1066
  }
1063
1067
  function useDeepLinks() {
1064
1068
  const { config } = useCollab();
1065
- const parseDeepLink = react.useCallback((link) => {
1069
+ const parseDeepLink = React4.useCallback((link) => {
1066
1070
  if (!link.startsWith(DEEP_LINK_PREFIX)) return null;
1067
1071
  const path = link.slice(DEEP_LINK_PREFIX.length);
1068
1072
  const parts = path.split("/").filter(Boolean);
@@ -1074,7 +1078,7 @@ function useDeepLinks() {
1074
1078
  id: parts[1]
1075
1079
  };
1076
1080
  }, []);
1077
- const handleDeepLink = react.useCallback(
1081
+ const handleDeepLink = React4.useCallback(
1078
1082
  (link) => {
1079
1083
  const parsed = parseDeepLink(link);
1080
1084
  if (!parsed) return;
@@ -1088,10 +1092,10 @@ function useDeepLinks() {
1088
1092
  },
1089
1093
  [parseDeepLink, config]
1090
1094
  );
1091
- const containsDeepLink = react.useCallback((text) => {
1095
+ const containsDeepLink = React4.useCallback((text) => {
1092
1096
  return text.includes(DEEP_LINK_PREFIX);
1093
1097
  }, []);
1094
- const extractDeepLinks = react.useCallback(
1098
+ const extractDeepLinks = React4.useCallback(
1095
1099
  (text) => {
1096
1100
  const regex = new RegExp(`${escapeRegex(DEEP_LINK_PREFIX)}[\\w/.-]+`, "g");
1097
1101
  const matches = text.match(regex) || [];
@@ -1281,7 +1285,7 @@ function SeenByIndicator({
1281
1285
  senderId,
1282
1286
  className
1283
1287
  }) {
1284
- const [expanded, setExpanded] = react.useState(false);
1288
+ const [expanded, setExpanded] = React4.useState(false);
1285
1289
  if (senderId !== currentUserId) return null;
1286
1290
  const seenByOthers = readBy.filter((id) => id !== currentUserId && id !== senderId).map((id) => participants.find((p) => p.userId === id)).filter((p) => !!p);
1287
1291
  if (seenByOthers.length === 0) return null;
@@ -1441,7 +1445,7 @@ function MessageBubble({
1441
1445
  pinDisabled = false,
1442
1446
  className
1443
1447
  }) {
1444
- const [isHovered, setIsHovered] = react.useState(false);
1448
+ const [isHovered, setIsHovered] = React4.useState(false);
1445
1449
  if (message.type === "system") {
1446
1450
  return /* @__PURE__ */ jsxRuntime.jsx(SystemBubble, { message });
1447
1451
  }
@@ -1757,7 +1761,83 @@ var styles5 = {
1757
1761
  color: "#d1d5db"
1758
1762
  }
1759
1763
  };
1760
- var MessageList = react.forwardRef(function MessageList2({
1764
+
1765
+ // src/core/dateLabels.ts
1766
+ var SHORT_MONTHS = [
1767
+ "Jan",
1768
+ "Feb",
1769
+ "Mar",
1770
+ "Apr",
1771
+ "May",
1772
+ "Jun",
1773
+ "Jul",
1774
+ "Aug",
1775
+ "Sep",
1776
+ "Oct",
1777
+ "Nov",
1778
+ "Dec"
1779
+ ];
1780
+ var LONG_WEEKDAYS = [
1781
+ "Sunday",
1782
+ "Monday",
1783
+ "Tuesday",
1784
+ "Wednesday",
1785
+ "Thursday",
1786
+ "Friday",
1787
+ "Saturday"
1788
+ ];
1789
+ var ONE_DAY_MS = 864e5;
1790
+ function parseSafe(iso) {
1791
+ const d = new Date(iso);
1792
+ return Number.isNaN(d.getTime()) ? null : d;
1793
+ }
1794
+ function startOfDay(d) {
1795
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
1796
+ }
1797
+ function daysBetween(a, b) {
1798
+ return Math.floor((startOfDay(a).getTime() - startOfDay(b).getTime()) / ONE_DAY_MS);
1799
+ }
1800
+ function formatFullDate(d) {
1801
+ return `${d.getDate()} ${SHORT_MONTHS[d.getMonth()]} ${d.getFullYear()}`;
1802
+ }
1803
+ function formatLocaleTime(d) {
1804
+ try {
1805
+ return d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
1806
+ } catch {
1807
+ const h = d.getHours();
1808
+ const m = d.getMinutes().toString().padStart(2, "0");
1809
+ const suffix = h >= 12 ? "PM" : "AM";
1810
+ const h12 = (h + 11) % 12 + 1;
1811
+ return `${h12}:${m} ${suffix}`;
1812
+ }
1813
+ }
1814
+ function formatInboxTimestamp(iso) {
1815
+ if (!iso) return "";
1816
+ const d = parseSafe(iso);
1817
+ if (!d) return "";
1818
+ const diffDays = daysBetween(/* @__PURE__ */ new Date(), d);
1819
+ if (diffDays === 0) return formatLocaleTime(d);
1820
+ if (diffDays === 1) return "Yesterday";
1821
+ if (diffDays > 1 && diffDays < 7) return LONG_WEEKDAYS[d.getDay()];
1822
+ return formatFullDate(d);
1823
+ }
1824
+ function formatDayDivider(iso) {
1825
+ if (!iso) return "";
1826
+ const d = parseSafe(iso);
1827
+ if (!d) return "";
1828
+ const diffDays = daysBetween(/* @__PURE__ */ new Date(), d);
1829
+ if (diffDays === 0) return "Today";
1830
+ if (diffDays === 1) return "Yesterday";
1831
+ if (diffDays > 1 && diffDays < 7) return LONG_WEEKDAYS[d.getDay()];
1832
+ return formatFullDate(d);
1833
+ }
1834
+ function dayBucketKey(iso) {
1835
+ if (!iso) return "";
1836
+ const d = parseSafe(iso);
1837
+ if (!d) return "";
1838
+ return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
1839
+ }
1840
+ var MessageList = React4.forwardRef(function MessageList2({
1761
1841
  messages,
1762
1842
  currentUserId,
1763
1843
  participants = [],
@@ -1775,10 +1855,10 @@ var MessageList = react.forwardRef(function MessageList2({
1775
1855
  pinDisabled,
1776
1856
  className
1777
1857
  }, ref) {
1778
- const containerRef = react.useRef(null);
1779
- const bottomRef = react.useRef(null);
1780
- const prevMessageCount = react.useRef(messages.length);
1781
- react.useImperativeHandle(
1858
+ const containerRef = React4.useRef(null);
1859
+ const bottomRef = React4.useRef(null);
1860
+ const prevMessageCount = React4.useRef(messages.length);
1861
+ React4.useImperativeHandle(
1782
1862
  ref,
1783
1863
  () => ({
1784
1864
  scrollToMessage: (messageId) => {
@@ -1795,7 +1875,7 @@ var MessageList = react.forwardRef(function MessageList2({
1795
1875
  }),
1796
1876
  []
1797
1877
  );
1798
- react.useEffect(() => {
1878
+ React4.useEffect(() => {
1799
1879
  if (messages.length > prevMessageCount.current) {
1800
1880
  const lastMessage = messages[messages.length - 1];
1801
1881
  const isOwnMessage = lastMessage?.senderId === currentUserId;
@@ -1805,7 +1885,7 @@ var MessageList = react.forwardRef(function MessageList2({
1805
1885
  }
1806
1886
  prevMessageCount.current = messages.length;
1807
1887
  }, [messages, currentUserId]);
1808
- react.useEffect(() => {
1888
+ React4.useEffect(() => {
1809
1889
  if (!onMessageVisible || !containerRef.current) return;
1810
1890
  const observer = new IntersectionObserver(
1811
1891
  (entries) => {
@@ -1822,7 +1902,7 @@ var MessageList = react.forwardRef(function MessageList2({
1822
1902
  messageElements.forEach((el) => observer.observe(el));
1823
1903
  return () => observer.disconnect();
1824
1904
  }, [messages, onMessageVisible]);
1825
- const handleScroll = react.useCallback(() => {
1905
+ const handleScroll = React4.useCallback(() => {
1826
1906
  if (!containerRef.current || !hasMore || isLoading) return;
1827
1907
  if (containerRef.current.scrollTop < 50) {
1828
1908
  onLoadMore();
@@ -1842,22 +1922,30 @@ var MessageList = react.forwardRef(function MessageList2({
1842
1922
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles6.emptyTitle, children: "No messages yet" }),
1843
1923
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles6.emptySubtitle, children: "Start the conversation about this study" })
1844
1924
  ] }),
1845
- messages.map((message) => /* @__PURE__ */ jsxRuntime.jsx("div", { "data-message-id": message.id, children: /* @__PURE__ */ jsxRuntime.jsx(
1846
- MessageBubble,
1847
- {
1848
- message,
1849
- isOwn: message.senderId === currentUserId,
1850
- participants,
1851
- currentUserId,
1852
- showSeenBy,
1853
- onDeepLinkClick,
1854
- onReply,
1855
- onPin,
1856
- onUnpin,
1857
- onCopy,
1858
- pinDisabled
1859
- }
1860
- ) }, message.id)),
1925
+ messages.map((message, index) => {
1926
+ const currentBucket = dayBucketKey(message.insertedAt);
1927
+ const prevBucket = index === 0 ? null : dayBucketKey(messages[index - 1].insertedAt);
1928
+ const showDivider = currentBucket && currentBucket !== prevBucket;
1929
+ return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
1930
+ showDivider && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayDividerPill, children: formatDayDivider(message.insertedAt) }) }),
1931
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "data-message-id": message.id, children: /* @__PURE__ */ jsxRuntime.jsx(
1932
+ MessageBubble,
1933
+ {
1934
+ message,
1935
+ isOwn: message.senderId === currentUserId,
1936
+ participants,
1937
+ currentUserId,
1938
+ showSeenBy,
1939
+ onDeepLinkClick,
1940
+ onReply,
1941
+ onPin,
1942
+ onUnpin,
1943
+ onCopy,
1944
+ pinDisabled
1945
+ }
1946
+ ) })
1947
+ ] }, message.id);
1948
+ }),
1861
1949
  typingUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles6.typingIndicator, children: [
1862
1950
  typingUsers.map((t) => t.userName ?? "Someone").join(", "),
1863
1951
  typingUsers.length === 1 ? " is " : " are ",
@@ -1919,109 +2007,24 @@ var styles6 = {
1919
2007
  fontSize: "12px",
1920
2008
  color: "#9ca3af",
1921
2009
  fontStyle: "italic"
2010
+ },
2011
+ dayDivider: {
2012
+ display: "flex",
2013
+ justifyContent: "center",
2014
+ margin: "12px 0 8px"
2015
+ },
2016
+ dayDividerPill: {
2017
+ display: "inline-block",
2018
+ padding: "4px 12px",
2019
+ fontSize: "12px",
2020
+ fontWeight: 500,
2021
+ color: "#475569",
2022
+ backgroundColor: "#f1f5f9",
2023
+ border: "1px solid #e2e8f0",
2024
+ borderRadius: "12px",
2025
+ letterSpacing: "0.2px"
1922
2026
  }
1923
2027
  };
1924
- function useAudioRecorder() {
1925
- const [isRecording, setIsRecording] = react.useState(false);
1926
- const [duration, setDuration] = react.useState(0);
1927
- const [error, setError] = react.useState(null);
1928
- const mediaRecorderRef = react.useRef(null);
1929
- const chunksRef = react.useRef([]);
1930
- const streamRef = react.useRef(null);
1931
- const timerRef = react.useRef(null);
1932
- const startTimeRef = react.useRef(0);
1933
- const resolveRef = react.useRef(null);
1934
- const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
1935
- const cleanup = react.useCallback(() => {
1936
- if (timerRef.current) {
1937
- clearInterval(timerRef.current);
1938
- timerRef.current = null;
1939
- }
1940
- if (streamRef.current) {
1941
- streamRef.current.getTracks().forEach((track) => track.stop());
1942
- streamRef.current = null;
1943
- }
1944
- mediaRecorderRef.current = null;
1945
- chunksRef.current = [];
1946
- setIsRecording(false);
1947
- setDuration(0);
1948
- }, []);
1949
- const start = react.useCallback(async () => {
1950
- if (!isSupported) {
1951
- setError("Audio recording is not supported in this browser");
1952
- return;
1953
- }
1954
- try {
1955
- setError(null);
1956
- chunksRef.current = [];
1957
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
1958
- streamRef.current = stream;
1959
- const mimeType = MediaRecorder.isTypeSupported(AUDIO_MIME_TYPE) ? AUDIO_MIME_TYPE : MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
1960
- const recorder = new MediaRecorder(stream, { mimeType });
1961
- mediaRecorderRef.current = recorder;
1962
- recorder.ondataavailable = (event) => {
1963
- if (event.data.size > 0) {
1964
- chunksRef.current.push(event.data);
1965
- }
1966
- };
1967
- recorder.onerror = () => {
1968
- setError("Recording failed");
1969
- cleanup();
1970
- resolveRef.current?.(null);
1971
- resolveRef.current = null;
1972
- };
1973
- recorder.onstop = () => {
1974
- const finalDuration = Math.round((Date.now() - startTimeRef.current) / 1e3);
1975
- const blob = new Blob(chunksRef.current, { type: mimeType });
1976
- cleanup();
1977
- resolveRef.current?.({ blob, duration: finalDuration });
1978
- resolveRef.current = null;
1979
- };
1980
- recorder.start(250);
1981
- startTimeRef.current = Date.now();
1982
- setIsRecording(true);
1983
- timerRef.current = setInterval(() => {
1984
- const elapsed = Math.round((Date.now() - startTimeRef.current) / 1e3);
1985
- setDuration(elapsed);
1986
- }, 1e3);
1987
- } catch (err) {
1988
- if (err instanceof DOMException && err.name === "NotAllowedError") {
1989
- setError("Microphone access denied. Please allow microphone permissions.");
1990
- } else {
1991
- setError("Failed to start recording");
1992
- }
1993
- cleanup();
1994
- }
1995
- }, [isSupported, cleanup]);
1996
- const stop = react.useCallback(async () => {
1997
- return new Promise((resolve) => {
1998
- if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
1999
- resolve(null);
2000
- return;
2001
- }
2002
- resolveRef.current = resolve;
2003
- mediaRecorderRef.current.stop();
2004
- });
2005
- }, []);
2006
- const cancel = react.useCallback(() => {
2007
- if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
2008
- mediaRecorderRef.current.onstop = null;
2009
- mediaRecorderRef.current.stop();
2010
- }
2011
- cleanup();
2012
- resolveRef.current?.(null);
2013
- resolveRef.current = null;
2014
- }, [cleanup]);
2015
- return {
2016
- isRecording,
2017
- duration,
2018
- start,
2019
- stop,
2020
- cancel,
2021
- isSupported,
2022
- error
2023
- };
2024
- }
2025
2028
  function ReplyPreview({ message, onCancel, className }) {
2026
2029
  const preview = renderQuotedPreview(message);
2027
2030
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles7.container, children: [
@@ -2114,23 +2117,10 @@ var styles7 = {
2114
2117
  flexShrink: 0
2115
2118
  }
2116
2119
  };
2117
- function MicIcon({ size = 20, color = "currentColor" }) {
2118
- return /* @__PURE__ */ jsxRuntime.jsx(
2119
- "svg",
2120
- {
2121
- xmlns: "http://www.w3.org/2000/svg",
2122
- width: size,
2123
- height: size,
2124
- viewBox: "0 0 24 24",
2125
- fill: color,
2126
- "aria-hidden": "true",
2127
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" })
2128
- }
2129
- );
2130
- }
2131
2120
  function MessageInput({
2132
2121
  onSendText,
2133
- onSendAudio,
2122
+ // MIC DISABLED — onSendAudio still accepted but unused.
2123
+ // onSendAudio,
2134
2124
  onSendFile,
2135
2125
  onTyping,
2136
2126
  replyTo,
@@ -2139,15 +2129,14 @@ function MessageInput({
2139
2129
  placeholder = "Type a message...",
2140
2130
  className
2141
2131
  }) {
2142
- const [text, setText] = react.useState("");
2143
- const [isSending, setIsSending] = react.useState(false);
2144
- const [fileError, setFileError] = react.useState(null);
2145
- const fileInputRef = react.useRef(null);
2146
- const typingTimeoutRef = react.useRef(null);
2147
- const isTypingRef = react.useRef(false);
2148
- const sendingRef = react.useRef(false);
2149
- const { isRecording, duration, start: startRecording, stop: stopRecording, cancel: cancelRecording, isSupported: micSupported, error: micError } = useAudioRecorder();
2150
- const handleTyping = react.useCallback(() => {
2132
+ const [text, setText] = React4.useState("");
2133
+ const [isSending, setIsSending] = React4.useState(false);
2134
+ const [fileError, setFileError] = React4.useState(null);
2135
+ const fileInputRef = React4.useRef(null);
2136
+ const typingTimeoutRef = React4.useRef(null);
2137
+ const isTypingRef = React4.useRef(false);
2138
+ const sendingRef = React4.useRef(false);
2139
+ const handleTyping = React4.useCallback(() => {
2151
2140
  if (!isTypingRef.current) {
2152
2141
  isTypingRef.current = true;
2153
2142
  onTyping(true);
@@ -2160,12 +2149,12 @@ function MessageInput({
2160
2149
  onTyping(false);
2161
2150
  }, 2e3);
2162
2151
  }, [onTyping]);
2163
- react.useEffect(() => {
2152
+ React4.useEffect(() => {
2164
2153
  return () => {
2165
2154
  if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
2166
2155
  };
2167
2156
  }, []);
2168
- const handleSendText = react.useCallback(async () => {
2157
+ const handleSendText = React4.useCallback(async () => {
2169
2158
  const trimmed = text.trim();
2170
2159
  if (!trimmed || sendingRef.current) return;
2171
2160
  sendingRef.current = true;
@@ -2186,7 +2175,7 @@ function MessageInput({
2186
2175
  setIsSending(false);
2187
2176
  }
2188
2177
  }, [text, onSendText, onTyping]);
2189
- const handleKeyDown = react.useCallback(
2178
+ const handleKeyDown = React4.useCallback(
2190
2179
  (e) => {
2191
2180
  if (e.key === "Enter" && !e.shiftKey) {
2192
2181
  e.preventDefault();
@@ -2195,18 +2184,7 @@ function MessageInput({
2195
2184
  },
2196
2185
  [handleSendText]
2197
2186
  );
2198
- const handleAudioStop = react.useCallback(async () => {
2199
- const result = await stopRecording();
2200
- if (result) {
2201
- setIsSending(true);
2202
- try {
2203
- await onSendAudio(result.blob, result.duration);
2204
- } finally {
2205
- setIsSending(false);
2206
- }
2207
- }
2208
- }, [stopRecording, onSendAudio]);
2209
- const handleFileSelect = react.useCallback(
2187
+ const handleFileSelect = React4.useCallback(
2210
2188
  async (e) => {
2211
2189
  const file = e.target.files?.[0];
2212
2190
  if (!file) return;
@@ -2227,29 +2205,8 @@ function MessageInput({
2227
2205
  );
2228
2206
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles8.container, children: [
2229
2207
  replyTo && onCancelReply && /* @__PURE__ */ jsxRuntime.jsx(ReplyPreview, { message: replyTo, onCancel: onCancelReply }),
2230
- (fileError || micError) && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles8.error, children: fileError || micError }),
2231
- isRecording ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles8.recordingBar, children: [
2232
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles8.recordingIndicator, children: [
2233
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles8.recordingDot }),
2234
- "Recording ",
2235
- formatDuration2(duration)
2236
- ] }),
2237
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles8.recordingActions, children: [
2238
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: cancelRecording, style: styles8.cancelButton, type: "button", children: "Cancel" }),
2239
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleAudioStop, style: styles8.stopButton, type: "button", children: "Send" })
2240
- ] })
2241
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles8.inputBar, children: [
2242
- micSupported && /* @__PURE__ */ jsxRuntime.jsx(
2243
- "button",
2244
- {
2245
- onClick: startRecording,
2246
- disabled: disabled || isSending,
2247
- style: styles8.iconButton,
2248
- title: "Record voice message",
2249
- type: "button",
2250
- children: /* @__PURE__ */ jsxRuntime.jsx(MicIcon, { size: 20, color: "#374151" })
2251
- }
2252
- ),
2208
+ fileError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles8.error, children: fileError }),
2209
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles8.inputBar, children: [
2253
2210
  /* @__PURE__ */ jsxRuntime.jsx(
2254
2211
  "button",
2255
2212
  {
@@ -2302,11 +2259,6 @@ function MessageInput({
2302
2259
  ] })
2303
2260
  ] });
2304
2261
  }
2305
- function formatDuration2(seconds) {
2306
- const m = Math.floor(seconds / 60);
2307
- const s = seconds % 60;
2308
- return `${m}:${s.toString().padStart(2, "0")}`;
2309
- }
2310
2262
  var styles8 = {
2311
2263
  container: {
2312
2264
  borderTop: "1px solid #e5e7eb",
@@ -2362,53 +2314,7 @@ var styles8 = {
2362
2314
  borderRadius: "50%",
2363
2315
  cursor: "pointer",
2364
2316
  flexShrink: 0
2365
- },
2366
- recordingBar: {
2367
- display: "flex",
2368
- justifyContent: "space-between",
2369
- alignItems: "center",
2370
- padding: "12px 16px",
2371
- backgroundColor: "#fef2f2"
2372
- },
2373
- recordingIndicator: {
2374
- display: "flex",
2375
- alignItems: "center",
2376
- gap: "8px",
2377
- fontSize: "14px",
2378
- color: "#dc2626",
2379
- fontWeight: 500
2380
- },
2381
- recordingDot: {
2382
- width: "8px",
2383
- height: "8px",
2384
- borderRadius: "50%",
2385
- backgroundColor: "#dc2626",
2386
- animation: "pulse 1.5s infinite"
2387
- },
2388
- recordingActions: {
2389
- display: "flex",
2390
- gap: "8px"
2391
- },
2392
- cancelButton: {
2393
- padding: "6px 14px",
2394
- fontSize: "13px",
2395
- color: "#6b7280",
2396
- backgroundColor: "#ffffff",
2397
- border: "1px solid #d1d5db",
2398
- borderRadius: "6px",
2399
- cursor: "pointer"
2400
- },
2401
- stopButton: {
2402
- padding: "6px 14px",
2403
- fontSize: "13px",
2404
- fontWeight: 500,
2405
- color: "#ffffff",
2406
- backgroundColor: "#dc2626",
2407
- border: "none",
2408
- borderRadius: "6px",
2409
- cursor: "pointer"
2410
- }
2411
- };
2317
+ }};
2412
2318
  function ParticipantsList({
2413
2319
  participants,
2414
2320
  currentUserId,
@@ -2574,13 +2480,13 @@ function ChannelSettings({
2574
2480
  onClose,
2575
2481
  className
2576
2482
  }) {
2577
- const [editingName, setEditingName] = react.useState(false);
2578
- const [nameValue, setNameValue] = react.useState(channelName);
2579
- const [showInvite, setShowInvite] = react.useState(false);
2580
- const [inviteId, setInviteId] = react.useState("");
2581
- const [inviteName, setInviteName] = react.useState("");
2582
- const [inviteRole, setInviteRole] = react.useState("radiologist");
2583
- const [confirmDelete, setConfirmDelete] = react.useState(false);
2483
+ const [editingName, setEditingName] = React4.useState(false);
2484
+ const [nameValue, setNameValue] = React4.useState(channelName);
2485
+ const [showInvite, setShowInvite] = React4.useState(false);
2486
+ const [inviteId, setInviteId] = React4.useState("");
2487
+ const [inviteName, setInviteName] = React4.useState("");
2488
+ const [inviteRole, setInviteRole] = React4.useState("radiologist");
2489
+ const [confirmDelete, setConfirmDelete] = React4.useState(false);
2584
2490
  const handleSaveName = async () => {
2585
2491
  if (nameValue.trim() && nameValue !== channelName) {
2586
2492
  await onUpdateChannel({ name: nameValue.trim() });
@@ -2871,7 +2777,7 @@ function PinnedMessagesBar({
2871
2777
  onUnpin,
2872
2778
  className
2873
2779
  }) {
2874
- const [index, setIndex] = react.useState(0);
2780
+ const [index, setIndex] = React4.useState(0);
2875
2781
  if (pinnedMessages.length === 0) return null;
2876
2782
  const safeIndex = Math.min(index, pinnedMessages.length - 1);
2877
2783
  const current = pinnedMessages[safeIndex];
@@ -3030,8 +2936,8 @@ function CollabPanel({
3030
2936
  style
3031
2937
  }) {
3032
2938
  const { config } = useCollab();
3033
- const [showSettings, setShowSettings] = react.useState(false);
3034
- const messageListRef = react.useRef(null);
2939
+ const [showSettings, setShowSettings] = React4.useState(false);
2940
+ const messageListRef = React4.useRef(null);
3035
2941
  const {
3036
2942
  conversation,
3037
2943
  messages,
@@ -3203,17 +3109,17 @@ function CollabPopup({
3203
3109
  height = 520,
3204
3110
  className
3205
3111
  }) {
3206
- const [position, setPosition] = react.useState(
3112
+ const [position, setPosition] = React4.useState(
3207
3113
  initialPosition || {
3208
3114
  x: window.innerWidth - width - 24,
3209
3115
  y: Math.max(24, window.innerHeight - height - 104)
3210
3116
  }
3211
3117
  );
3212
- const [isDragging, setIsDragging] = react.useState(false);
3213
- const [isMinimized, setIsMinimized] = react.useState(false);
3214
- const dragOffset = react.useRef({ x: 0, y: 0 });
3215
- const containerRef = react.useRef(null);
3216
- const handleMouseDown = react.useCallback(
3118
+ const [isDragging, setIsDragging] = React4.useState(false);
3119
+ const [isMinimized, setIsMinimized] = React4.useState(false);
3120
+ const dragOffset = React4.useRef({ x: 0, y: 0 });
3121
+ const containerRef = React4.useRef(null);
3122
+ const handleMouseDown = React4.useCallback(
3217
3123
  (e) => {
3218
3124
  if (!e.target.closest("[data-drag-handle]")) return;
3219
3125
  setIsDragging(true);
@@ -3225,7 +3131,7 @@ function CollabPopup({
3225
3131
  },
3226
3132
  [position]
3227
3133
  );
3228
- react.useEffect(() => {
3134
+ React4.useEffect(() => {
3229
3135
  if (!isOpen) return;
3230
3136
  const handleKey = (e) => {
3231
3137
  if (e.key === "Escape") {
@@ -3235,7 +3141,7 @@ function CollabPopup({
3235
3141
  window.addEventListener("keydown", handleKey);
3236
3142
  return () => window.removeEventListener("keydown", handleKey);
3237
3143
  }, [isOpen, onClose]);
3238
- react.useEffect(() => {
3144
+ React4.useEffect(() => {
3239
3145
  if (!isDragging) return;
3240
3146
  const handleMouseMove = (e) => {
3241
3147
  setPosition({
@@ -3358,25 +3264,25 @@ function useInlineCollab({
3358
3264
  invalidatePreview,
3359
3265
  createConversationWithMessage
3360
3266
  } = useCollab();
3361
- const [preview, setPreview] = react.useState(null);
3362
- const [messages, setMessages] = react.useState([]);
3363
- const [participants, setParticipants] = react.useState([]);
3364
- const [unreadCount, setUnreadCount] = react.useState(0);
3365
- const [isLoading, setIsLoading] = react.useState(true);
3366
- const [isSubscribed, setIsSubscribed] = react.useState(false);
3367
- const [error, setError] = react.useState(null);
3368
- const elementRef = react.useRef(null);
3369
- const observerRef = react.useRef(null);
3370
- const subscribedConversationIdRef = react.useRef(null);
3371
- const trimToLimit = react.useCallback(
3267
+ const [preview, setPreview] = React4.useState(null);
3268
+ const [messages, setMessages] = React4.useState([]);
3269
+ const [participants, setParticipants] = React4.useState([]);
3270
+ const [unreadCount, setUnreadCount] = React4.useState(0);
3271
+ const [isLoading, setIsLoading] = React4.useState(true);
3272
+ const [isSubscribed, setIsSubscribed] = React4.useState(false);
3273
+ const [error, setError] = React4.useState(null);
3274
+ const elementRef = React4.useRef(null);
3275
+ const observerRef = React4.useRef(null);
3276
+ const subscribedConversationIdRef = React4.useRef(null);
3277
+ const trimToLimit = React4.useCallback(
3372
3278
  (msgs) => msgs.length > messageLimit ? msgs.slice(msgs.length - messageLimit) : msgs,
3373
3279
  [messageLimit]
3374
3280
  );
3375
- const buildName = react.useCallback(
3281
+ const buildName = React4.useCallback(
3376
3282
  () => buildChannelName(patientData),
3377
3283
  [patientData]
3378
3284
  );
3379
- react.useEffect(() => {
3285
+ React4.useEffect(() => {
3380
3286
  let cancelled = false;
3381
3287
  const load = async () => {
3382
3288
  setIsLoading(true);
@@ -3402,7 +3308,7 @@ function useInlineCollab({
3402
3308
  cancelled = true;
3403
3309
  };
3404
3310
  }, [orderId, requestPreview, trimToLimit]);
3405
- const subscribe = react.useCallback(
3311
+ const subscribe = React4.useCallback(
3406
3312
  (conversationId) => {
3407
3313
  if (subscribedConversationIdRef.current === conversationId) return;
3408
3314
  socket.joinConversation(conversationId, {
@@ -3426,7 +3332,7 @@ function useInlineCollab({
3426
3332
  },
3427
3333
  [socket, trimToLimit, config.userId]
3428
3334
  );
3429
- const unsubscribe = react.useCallback(() => {
3335
+ const unsubscribe = React4.useCallback(() => {
3430
3336
  const convId = subscribedConversationIdRef.current;
3431
3337
  if (convId) {
3432
3338
  socket.leaveConversation(convId);
@@ -3434,7 +3340,7 @@ function useInlineCollab({
3434
3340
  setIsSubscribed(false);
3435
3341
  }
3436
3342
  }, [socket]);
3437
- const containerRef = react.useCallback(
3343
+ const containerRef = React4.useCallback(
3438
3344
  (element) => {
3439
3345
  if (observerRef.current) {
3440
3346
  observerRef.current.disconnect();
@@ -3460,18 +3366,18 @@ function useInlineCollab({
3460
3366
  },
3461
3367
  [preview, subscribe, unsubscribe]
3462
3368
  );
3463
- react.useEffect(() => {
3369
+ React4.useEffect(() => {
3464
3370
  return () => {
3465
3371
  if (observerRef.current) observerRef.current.disconnect();
3466
3372
  unsubscribe();
3467
3373
  };
3468
3374
  }, [unsubscribe]);
3469
- react.useEffect(() => {
3375
+ React4.useEffect(() => {
3470
3376
  if (preview?.conversationId && elementRef.current && !observerRef.current) {
3471
3377
  containerRef(elementRef.current);
3472
3378
  }
3473
3379
  }, [preview, containerRef]);
3474
- const dispatchSend = react.useCallback(
3380
+ const dispatchSend = React4.useCallback(
3475
3381
  async (payload) => {
3476
3382
  if (!preview) {
3477
3383
  try {
@@ -3528,7 +3434,7 @@ function useInlineCollab({
3528
3434
  config
3529
3435
  ]
3530
3436
  );
3531
- const sendMessage = react.useCallback(
3437
+ const sendMessage = React4.useCallback(
3532
3438
  async (body) => {
3533
3439
  const trimmed = body.trim();
3534
3440
  if (!trimmed) return;
@@ -3536,7 +3442,7 @@ function useInlineCollab({
3536
3442
  },
3537
3443
  [dispatchSend]
3538
3444
  );
3539
- const sendAudioMessage = react.useCallback(
3445
+ const sendAudioMessage = React4.useCallback(
3540
3446
  async (audioBlob, duration) => {
3541
3447
  if (!config.onUploadFile) {
3542
3448
  setError("File upload is not configured");
@@ -3649,10 +3555,10 @@ function RoleDot({ role }) {
3649
3555
  return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles13.roleDot, backgroundColor: colors[role] } });
3650
3556
  }
3651
3557
  function InlineAudioPlayer({ url, duration }) {
3652
- const audioRef = react.useRef(null);
3653
- const [isPlaying, setIsPlaying] = react.useState(false);
3654
- const [currentTime, setCurrentTime] = react.useState(0);
3655
- const toggle = react.useCallback(
3558
+ const audioRef = React4.useRef(null);
3559
+ const [isPlaying, setIsPlaying] = React4.useState(false);
3560
+ const [currentTime, setCurrentTime] = React4.useState(0);
3561
+ const toggle = React4.useCallback(
3656
3562
  (e) => {
3657
3563
  e.stopPropagation();
3658
3564
  const audio = audioRef.current;
@@ -3666,7 +3572,7 @@ function InlineAudioPlayer({ url, duration }) {
3666
3572
  },
3667
3573
  [isPlaying]
3668
3574
  );
3669
- react.useEffect(() => {
3575
+ React4.useEffect(() => {
3670
3576
  const audio = audioRef.current;
3671
3577
  if (!audio) return;
3672
3578
  const onPlay = () => setIsPlaying(true);
@@ -3710,7 +3616,7 @@ function InlineAudioPlayer({ url, duration }) {
3710
3616
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles13.waveBar, height: "70%" } }),
3711
3617
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles13.waveBar, height: "40%" } })
3712
3618
  ] }),
3713
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles13.audioDuration, children: formatDuration3(displayTime) }),
3619
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles13.audioDuration, children: formatDuration2(displayTime) }),
3714
3620
  /* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, src: url, preload: "metadata" })
3715
3621
  ] });
3716
3622
  }
@@ -3726,7 +3632,7 @@ function renderMessagePreview(message) {
3726
3632
  return message.body;
3727
3633
  }
3728
3634
  }
3729
- function formatDuration3(seconds) {
3635
+ function formatDuration2(seconds) {
3730
3636
  const safe = Math.max(0, Math.floor(seconds));
3731
3637
  const m = Math.floor(safe / 60);
3732
3638
  const s = safe % 60;
@@ -3734,26 +3640,18 @@ function formatDuration3(seconds) {
3734
3640
  }
3735
3641
  function InlineInputBar({
3736
3642
  onSend,
3737
- onSendAudio,
3643
+ // MIC DISABLED — onSendAudio accepted but unused.
3644
+ // onSendAudio,
3738
3645
  placeholder,
3739
3646
  unreadCount,
3740
3647
  isLive,
3741
3648
  showExpand,
3742
3649
  onExpand
3743
3650
  }) {
3744
- const [text, setText] = react.useState("");
3745
- const [isSending, setIsSending] = react.useState(false);
3746
- const inputRef = react.useRef(null);
3747
- const {
3748
- isRecording,
3749
- duration,
3750
- start: startRecording,
3751
- stop: stopRecording,
3752
- cancel: cancelRecording,
3753
- isSupported: micSupported,
3754
- error: micError
3755
- } = useAudioRecorder();
3756
- const handleSend = react.useCallback(async () => {
3651
+ const [text, setText] = React4.useState("");
3652
+ const [isSending, setIsSending] = React4.useState(false);
3653
+ const inputRef = React4.useRef(null);
3654
+ const handleSend = React4.useCallback(async () => {
3757
3655
  const trimmed = text.trim();
3758
3656
  if (!trimmed || isSending) return;
3759
3657
  setIsSending(true);
@@ -3765,7 +3663,7 @@ function InlineInputBar({
3765
3663
  setIsSending(false);
3766
3664
  }
3767
3665
  }, [text, isSending, onSend]);
3768
- const handleKeyDown = react.useCallback(
3666
+ const handleKeyDown = React4.useCallback(
3769
3667
  (e) => {
3770
3668
  if (e.key === "Enter" && !e.shiftKey) {
3771
3669
  e.preventDefault();
@@ -3774,46 +3672,6 @@ function InlineInputBar({
3774
3672
  },
3775
3673
  [handleSend]
3776
3674
  );
3777
- const handleStopRecording = react.useCallback(async () => {
3778
- const result = await stopRecording();
3779
- if (result) {
3780
- setIsSending(true);
3781
- try {
3782
- await onSendAudio(result.blob, result.duration);
3783
- } finally {
3784
- setIsSending(false);
3785
- }
3786
- }
3787
- }, [stopRecording, onSendAudio]);
3788
- if (isRecording) {
3789
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles13.recordingBar, children: [
3790
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles13.recordingDot }),
3791
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles13.recordingLabel, children: [
3792
- "Recording ",
3793
- formatDuration3(duration)
3794
- ] }),
3795
- /* @__PURE__ */ jsxRuntime.jsx(
3796
- "button",
3797
- {
3798
- onClick: cancelRecording,
3799
- style: styles13.recordingCancel,
3800
- title: "Cancel recording",
3801
- type: "button",
3802
- children: "Cancel"
3803
- }
3804
- ),
3805
- /* @__PURE__ */ jsxRuntime.jsx(
3806
- "button",
3807
- {
3808
- onClick: handleStopRecording,
3809
- style: styles13.recordingSend,
3810
- title: "Send voice message",
3811
- type: "button",
3812
- children: "Send"
3813
- }
3814
- )
3815
- ] });
3816
- }
3817
3675
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles13.inputBar, children: [
3818
3676
  /* @__PURE__ */ jsxRuntime.jsx(
3819
3677
  "input",
@@ -3823,24 +3681,13 @@ function InlineInputBar({
3823
3681
  value: text,
3824
3682
  onChange: (e) => setText(e.target.value),
3825
3683
  onKeyDown: handleKeyDown,
3826
- placeholder: micError ?? placeholder,
3684
+ placeholder,
3827
3685
  disabled: isSending,
3828
3686
  style: styles13.input
3829
3687
  }
3830
3688
  ),
3831
3689
  unreadCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles13.unreadBadge, title: `${unreadCount} unread`, children: unreadCount > 99 ? "99+" : unreadCount }),
3832
3690
  isLive && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles13.liveDot, title: "Live updates active" }),
3833
- micSupported && !text.trim() && /* @__PURE__ */ jsxRuntime.jsx(
3834
- "button",
3835
- {
3836
- onClick: startRecording,
3837
- disabled: isSending,
3838
- style: styles13.micButton,
3839
- title: "Record voice message",
3840
- type: "button",
3841
- children: /* @__PURE__ */ jsxRuntime.jsx(MicIcon, { size: 16, color: "#374151" })
3842
- }
3843
- ),
3844
3691
  text.trim() && /* @__PURE__ */ jsxRuntime.jsx(
3845
3692
  "button",
3846
3693
  {
@@ -4018,19 +3865,6 @@ var styles13 = {
4018
3865
  borderRadius: "50%",
4019
3866
  backgroundColor: "#22c55e"
4020
3867
  },
4021
- micButton: {
4022
- flexShrink: 0,
4023
- width: "28px",
4024
- height: "28px",
4025
- display: "flex",
4026
- alignItems: "center",
4027
- justifyContent: "center",
4028
- fontSize: "13px",
4029
- backgroundColor: "#f3f4f6",
4030
- border: "none",
4031
- borderRadius: "50%",
4032
- cursor: "pointer"
4033
- },
4034
3868
  sendButton: {
4035
3869
  flexShrink: 0,
4036
3870
  width: "28px",
@@ -4058,61 +3892,14 @@ var styles13 = {
4058
3892
  border: "1px solid #e5e7eb",
4059
3893
  borderRadius: "50%",
4060
3894
  cursor: "pointer"
4061
- },
4062
- // ── Recording mode ──
4063
- recordingBar: {
4064
- display: "flex",
4065
- alignItems: "center",
4066
- gap: "8px",
4067
- padding: "6px 10px",
4068
- backgroundColor: "#fef2f2",
4069
- border: "1px solid #fecaca",
4070
- borderRadius: "16px"
4071
- },
4072
- recordingDot: {
4073
- width: "8px",
4074
- height: "8px",
4075
- borderRadius: "50%",
4076
- backgroundColor: "#dc2626",
4077
- flexShrink: 0,
4078
- animation: "pulse 1.5s infinite"
4079
- },
4080
- recordingLabel: {
4081
- flex: 1,
4082
- fontSize: "12px",
4083
- fontWeight: 500,
4084
- color: "#dc2626",
4085
- fontVariantNumeric: "tabular-nums"
4086
- },
4087
- recordingCancel: {
4088
- padding: "4px 10px",
4089
- fontSize: "11px",
4090
- color: "#6b7280",
4091
- backgroundColor: "#ffffff",
4092
- border: "1px solid #d1d5db",
4093
- borderRadius: "12px",
4094
- cursor: "pointer",
4095
- flexShrink: 0
4096
- },
4097
- recordingSend: {
4098
- padding: "4px 10px",
4099
- fontSize: "11px",
4100
- fontWeight: 500,
4101
- color: "#ffffff",
4102
- backgroundColor: "#dc2626",
4103
- border: "none",
4104
- borderRadius: "12px",
4105
- cursor: "pointer",
4106
- flexShrink: 0
4107
- }
4108
- };
3895
+ }};
4109
3896
  function useConversationList(options) {
4110
3897
  const enabled = options?.enabled ?? true;
4111
3898
  const { fetchConversationList, config, totalUnread: serverTotalUnread } = useCollab();
4112
- const [conversations, setConversations] = react.useState([]);
4113
- const [isLoading, setIsLoading] = react.useState(enabled);
4114
- const [error, setError] = react.useState(null);
4115
- const silentRefresh = react.useCallback(async () => {
3899
+ const [conversations, setConversations] = React4.useState([]);
3900
+ const [isLoading, setIsLoading] = React4.useState(enabled);
3901
+ const [error, setError] = React4.useState(null);
3902
+ const silentRefresh = React4.useCallback(async () => {
4116
3903
  try {
4117
3904
  const list = await fetchConversationList();
4118
3905
  setConversations(list);
@@ -4124,7 +3911,7 @@ function useConversationList(options) {
4124
3911
  });
4125
3912
  }
4126
3913
  }, [fetchConversationList, config]);
4127
- const refresh = react.useCallback(async () => {
3914
+ const refresh = React4.useCallback(async () => {
4128
3915
  setIsLoading(true);
4129
3916
  setError(null);
4130
3917
  try {
@@ -4142,12 +3929,12 @@ function useConversationList(options) {
4142
3929
  setIsLoading(false);
4143
3930
  }
4144
3931
  }, [fetchConversationList, config]);
4145
- react.useEffect(() => {
3932
+ React4.useEffect(() => {
4146
3933
  if (!enabled) return;
4147
3934
  refresh();
4148
3935
  }, [enabled, refresh]);
4149
- const hasHandledInitialUnreadRef = react.useRef(false);
4150
- react.useEffect(() => {
3936
+ const hasHandledInitialUnreadRef = React4.useRef(false);
3937
+ React4.useEffect(() => {
4151
3938
  if (!enabled) return;
4152
3939
  if (!hasHandledInitialUnreadRef.current) {
4153
3940
  hasHandledInitialUnreadRef.current = true;
@@ -4155,7 +3942,7 @@ function useConversationList(options) {
4155
3942
  }
4156
3943
  silentRefresh();
4157
3944
  }, [serverTotalUnread, enabled]);
4158
- const filter = react.useCallback(
3945
+ const filter = React4.useCallback(
4159
3946
  (query) => {
4160
3947
  const q = query.trim().toLowerCase();
4161
3948
  if (!q) return conversations;
@@ -4220,7 +4007,7 @@ function ConversationListItem({
4220
4007
  ...styles14.time,
4221
4008
  ...hasUnread ? styles14.timeUnread : {}
4222
4009
  },
4223
- children: formatRelativeTime(item.lastActivityAt)
4010
+ children: formatInboxTimestamp(item.lastActivityAt)
4224
4011
  }
4225
4012
  )
4226
4013
  ] }),
@@ -4263,23 +4050,6 @@ function renderLastMessagePreview(message) {
4263
4050
  }
4264
4051
  }
4265
4052
  }
4266
- function formatRelativeTime(iso) {
4267
- try {
4268
- const date = new Date(iso);
4269
- const now = /* @__PURE__ */ new Date();
4270
- const diffMs = now.getTime() - date.getTime();
4271
- const diffMin = Math.floor(diffMs / 6e4);
4272
- const diffHr = Math.floor(diffMs / 36e5);
4273
- const diffDay = Math.floor(diffMs / 864e5);
4274
- if (diffMin < 1) return "now";
4275
- if (diffMin < 60) return `${diffMin}m`;
4276
- if (diffHr < 24) return `${diffHr}h`;
4277
- if (diffDay < 7) return `${diffDay}d`;
4278
- return date.toLocaleDateString([], { month: "short", day: "numeric" });
4279
- } catch {
4280
- return "";
4281
- }
4282
- }
4283
4053
  var styles14 = {
4284
4054
  container: {
4285
4055
  display: "flex",
@@ -4409,9 +4179,9 @@ function ConversationList({
4409
4179
  title = "Conversations",
4410
4180
  className
4411
4181
  }) {
4412
- const [query, setQuery] = react.useState("");
4413
- const [showUnreadOnly, setShowUnreadOnly] = react.useState(false);
4414
- const filtered = react.useMemo(() => {
4182
+ const [query, setQuery] = React4.useState("");
4183
+ const [showUnreadOnly, setShowUnreadOnly] = React4.useState(false);
4184
+ const filtered = React4.useMemo(() => {
4415
4185
  let result = conversations;
4416
4186
  if (showUnreadOnly) {
4417
4187
  result = result.filter((c) => c.unreadCount > 0);
@@ -4588,7 +4358,7 @@ function CollabInbox({
4588
4358
  style
4589
4359
  }) {
4590
4360
  const { conversations, isLoading, error } = useConversationList();
4591
- const [selectedId, setSelectedId] = react.useState(initialConversationId ?? null);
4361
+ const [selectedId, setSelectedId] = React4.useState(initialConversationId ?? null);
4592
4362
  const selected = conversations.find((c) => c.id === selectedId) || null;
4593
4363
  const handleSelect = (item) => {
4594
4364
  setSelectedId(item.id);
@@ -4690,12 +4460,12 @@ function useMessages({
4690
4460
  initialLimit = MESSAGES_PAGE_SIZE
4691
4461
  }) {
4692
4462
  const { fetchMessages, config } = useCollab();
4693
- const [messages, setMessages] = react.useState([]);
4694
- const [isLoading, setIsLoading] = react.useState(false);
4695
- const [hasMore, setHasMore] = react.useState(true);
4696
- const [error, setError] = react.useState(null);
4697
- const loadedRef = react.useRef(null);
4698
- react.useEffect(() => {
4463
+ const [messages, setMessages] = React4.useState([]);
4464
+ const [isLoading, setIsLoading] = React4.useState(false);
4465
+ const [hasMore, setHasMore] = React4.useState(true);
4466
+ const [error, setError] = React4.useState(null);
4467
+ const loadedRef = React4.useRef(null);
4468
+ React4.useEffect(() => {
4699
4469
  if (!conversationId) {
4700
4470
  setMessages([]);
4701
4471
  setHasMore(false);
@@ -4731,7 +4501,7 @@ function useMessages({
4731
4501
  cancelled = true;
4732
4502
  };
4733
4503
  }, [conversationId, initialLimit, fetchMessages, config]);
4734
- const loadMore = react.useCallback(async () => {
4504
+ const loadMore = React4.useCallback(async () => {
4735
4505
  if (!conversationId || !hasMore || isLoading) return;
4736
4506
  const oldestId = messages[0]?.id;
4737
4507
  setIsLoading(true);
@@ -4752,17 +4522,17 @@ function useMessages({
4752
4522
  setIsLoading(false);
4753
4523
  }
4754
4524
  }, [conversationId, hasMore, isLoading, messages, initialLimit, fetchMessages, config]);
4755
- const addMessage = react.useCallback((message) => {
4525
+ const addMessage = React4.useCallback((message) => {
4756
4526
  setMessages((prev) => [...prev, message]);
4757
4527
  }, []);
4758
- const updateMessageReadBy = react.useCallback((messageId, userId) => {
4528
+ const updateMessageReadBy = React4.useCallback((messageId, userId) => {
4759
4529
  setMessages(
4760
4530
  (prev) => prev.map(
4761
4531
  (msg) => msg.id === messageId && !(msg.readBy ?? []).includes(userId) ? { ...msg, readBy: [...msg.readBy ?? [], userId] } : msg
4762
4532
  )
4763
4533
  );
4764
4534
  }, []);
4765
- const reset = react.useCallback(() => {
4535
+ const reset = React4.useCallback(() => {
4766
4536
  setMessages([]);
4767
4537
  setHasMore(true);
4768
4538
  setError(null);
@@ -4779,21 +4549,122 @@ function useMessages({
4779
4549
  reset
4780
4550
  };
4781
4551
  }
4552
+ function useAudioRecorder() {
4553
+ const [isRecording, setIsRecording] = React4.useState(false);
4554
+ const [duration, setDuration] = React4.useState(0);
4555
+ const [error, setError] = React4.useState(null);
4556
+ const mediaRecorderRef = React4.useRef(null);
4557
+ const chunksRef = React4.useRef([]);
4558
+ const streamRef = React4.useRef(null);
4559
+ const timerRef = React4.useRef(null);
4560
+ const startTimeRef = React4.useRef(0);
4561
+ const resolveRef = React4.useRef(null);
4562
+ const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
4563
+ const cleanup = React4.useCallback(() => {
4564
+ if (timerRef.current) {
4565
+ clearInterval(timerRef.current);
4566
+ timerRef.current = null;
4567
+ }
4568
+ if (streamRef.current) {
4569
+ streamRef.current.getTracks().forEach((track) => track.stop());
4570
+ streamRef.current = null;
4571
+ }
4572
+ mediaRecorderRef.current = null;
4573
+ chunksRef.current = [];
4574
+ setIsRecording(false);
4575
+ setDuration(0);
4576
+ }, []);
4577
+ const start = React4.useCallback(async () => {
4578
+ if (!isSupported) {
4579
+ setError("Audio recording is not supported in this browser");
4580
+ return;
4581
+ }
4582
+ try {
4583
+ setError(null);
4584
+ chunksRef.current = [];
4585
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
4586
+ streamRef.current = stream;
4587
+ const mimeType = MediaRecorder.isTypeSupported(AUDIO_MIME_TYPE) ? AUDIO_MIME_TYPE : MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
4588
+ const recorder = new MediaRecorder(stream, { mimeType });
4589
+ mediaRecorderRef.current = recorder;
4590
+ recorder.ondataavailable = (event) => {
4591
+ if (event.data.size > 0) {
4592
+ chunksRef.current.push(event.data);
4593
+ }
4594
+ };
4595
+ recorder.onerror = () => {
4596
+ setError("Recording failed");
4597
+ cleanup();
4598
+ resolveRef.current?.(null);
4599
+ resolveRef.current = null;
4600
+ };
4601
+ recorder.onstop = () => {
4602
+ const finalDuration = Math.round((Date.now() - startTimeRef.current) / 1e3);
4603
+ const blob = new Blob(chunksRef.current, { type: mimeType });
4604
+ cleanup();
4605
+ resolveRef.current?.({ blob, duration: finalDuration });
4606
+ resolveRef.current = null;
4607
+ };
4608
+ recorder.start(250);
4609
+ startTimeRef.current = Date.now();
4610
+ setIsRecording(true);
4611
+ timerRef.current = setInterval(() => {
4612
+ const elapsed = Math.round((Date.now() - startTimeRef.current) / 1e3);
4613
+ setDuration(elapsed);
4614
+ }, 1e3);
4615
+ } catch (err) {
4616
+ if (err instanceof DOMException && err.name === "NotAllowedError") {
4617
+ setError("Microphone access denied. Please allow microphone permissions.");
4618
+ } else {
4619
+ setError("Failed to start recording");
4620
+ }
4621
+ cleanup();
4622
+ }
4623
+ }, [isSupported, cleanup]);
4624
+ const stop = React4.useCallback(async () => {
4625
+ return new Promise((resolve) => {
4626
+ if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
4627
+ resolve(null);
4628
+ return;
4629
+ }
4630
+ resolveRef.current = resolve;
4631
+ mediaRecorderRef.current.stop();
4632
+ });
4633
+ }, []);
4634
+ const cancel = React4.useCallback(() => {
4635
+ if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
4636
+ mediaRecorderRef.current.onstop = null;
4637
+ mediaRecorderRef.current.stop();
4638
+ }
4639
+ cleanup();
4640
+ resolveRef.current?.(null);
4641
+ resolveRef.current = null;
4642
+ }, [cleanup]);
4643
+ return {
4644
+ isRecording,
4645
+ duration,
4646
+ start,
4647
+ stop,
4648
+ cancel,
4649
+ isSupported,
4650
+ error
4651
+ };
4652
+ }
4782
4653
  function useUnreadCount() {
4783
4654
  const { socket, totalUnread } = useCollab();
4784
- const [counts, setCounts] = react.useState({});
4785
- react.useEffect(() => {
4655
+ const [counts, setCounts] = React4.useState({});
4656
+ React4.useEffect(() => {
4786
4657
  socket.onUnreadCountUpdate((serverCounts) => {
4787
4658
  setCounts(serverCounts);
4788
4659
  });
4789
4660
  }, [socket]);
4790
- const getCountForConversation = react.useCallback(
4661
+ const getCountForConversation = React4.useCallback(
4791
4662
  (conversationId) => {
4792
4663
  return counts[conversationId] ?? 0;
4793
4664
  },
4794
4665
  [counts]
4795
4666
  );
4796
- const clearForConversation = react.useCallback((conversationId) => {
4667
+ const clearForConversation = React4.useCallback((conversationId) => {
4797
4668
  setCounts((prev) => {
4798
4669
  const next = { ...prev };
4799
4670
  delete next[conversationId];
@@ -4811,10 +4682,10 @@ function usePinnedMessages({
4811
4682
  initial = []
4812
4683
  }) {
4813
4684
  const { pinMessage, unpinMessage, fetchPinnedMessages, config } = useCollab();
4814
- const [pinnedMessages, setPinnedMessages] = react.useState(initial);
4815
- const [isLoading, setIsLoading] = react.useState(false);
4816
- const [error, setError] = react.useState(null);
4817
- const refresh = react.useCallback(async () => {
4685
+ const [pinnedMessages, setPinnedMessages] = React4.useState(initial);
4686
+ const [isLoading, setIsLoading] = React4.useState(false);
4687
+ const [error, setError] = React4.useState(null);
4688
+ const refresh = React4.useCallback(async () => {
4818
4689
  if (!conversationId) return;
4819
4690
  setIsLoading(true);
4820
4691
  setError(null);
@@ -4828,12 +4699,12 @@ function usePinnedMessages({
4828
4699
  setIsLoading(false);
4829
4700
  }
4830
4701
  }, [conversationId, fetchPinnedMessages]);
4831
- react.useEffect(() => {
4702
+ React4.useEffect(() => {
4832
4703
  if (conversationId && initial.length === 0) {
4833
4704
  refresh();
4834
4705
  }
4835
4706
  }, [conversationId]);
4836
- const pin = react.useCallback(
4707
+ const pin = React4.useCallback(
4837
4708
  async (messageId) => {
4838
4709
  if (!conversationId) return;
4839
4710
  setError(null);
@@ -4851,7 +4722,7 @@ function usePinnedMessages({
4851
4722
  },
4852
4723
  [conversationId, pinMessage, config]
4853
4724
  );
4854
- const unpin = react.useCallback(
4725
+ const unpin = React4.useCallback(
4855
4726
  async (messageId) => {
4856
4727
  if (!conversationId) return;
4857
4728
  setError(null);
@@ -4869,13 +4740,13 @@ function usePinnedMessages({
4869
4740
  },
4870
4741
  [conversationId, unpinMessage, config]
4871
4742
  );
4872
- const handlePinned = react.useCallback((message) => {
4743
+ const handlePinned = React4.useCallback((message) => {
4873
4744
  setPinnedMessages((prev) => {
4874
4745
  if (prev.some((m) => m.id === message.id)) return prev;
4875
4746
  return [...prev, message];
4876
4747
  });
4877
4748
  }, []);
4878
- const handleUnpinned = react.useCallback((messageId) => {
4749
+ const handleUnpinned = React4.useCallback((messageId) => {
4879
4750
  setPinnedMessages((prev) => prev.filter((m) => m.id !== messageId));
4880
4751
  }, []);
4881
4752
  return {