@natoe/colab 0.1.1 → 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,6 +2007,22 @@ 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
2028
  function ReplyPreview({ message, onCancel, className }) {
@@ -2025,14 +2129,14 @@ function MessageInput({
2025
2129
  placeholder = "Type a message...",
2026
2130
  className
2027
2131
  }) {
2028
- const [text, setText] = react.useState("");
2029
- const [isSending, setIsSending] = react.useState(false);
2030
- const [fileError, setFileError] = react.useState(null);
2031
- const fileInputRef = react.useRef(null);
2032
- const typingTimeoutRef = react.useRef(null);
2033
- const isTypingRef = react.useRef(false);
2034
- const sendingRef = react.useRef(false);
2035
- 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(() => {
2036
2140
  if (!isTypingRef.current) {
2037
2141
  isTypingRef.current = true;
2038
2142
  onTyping(true);
@@ -2045,12 +2149,12 @@ function MessageInput({
2045
2149
  onTyping(false);
2046
2150
  }, 2e3);
2047
2151
  }, [onTyping]);
2048
- react.useEffect(() => {
2152
+ React4.useEffect(() => {
2049
2153
  return () => {
2050
2154
  if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
2051
2155
  };
2052
2156
  }, []);
2053
- const handleSendText = react.useCallback(async () => {
2157
+ const handleSendText = React4.useCallback(async () => {
2054
2158
  const trimmed = text.trim();
2055
2159
  if (!trimmed || sendingRef.current) return;
2056
2160
  sendingRef.current = true;
@@ -2071,7 +2175,7 @@ function MessageInput({
2071
2175
  setIsSending(false);
2072
2176
  }
2073
2177
  }, [text, onSendText, onTyping]);
2074
- const handleKeyDown = react.useCallback(
2178
+ const handleKeyDown = React4.useCallback(
2075
2179
  (e) => {
2076
2180
  if (e.key === "Enter" && !e.shiftKey) {
2077
2181
  e.preventDefault();
@@ -2080,7 +2184,7 @@ function MessageInput({
2080
2184
  },
2081
2185
  [handleSendText]
2082
2186
  );
2083
- const handleFileSelect = react.useCallback(
2187
+ const handleFileSelect = React4.useCallback(
2084
2188
  async (e) => {
2085
2189
  const file = e.target.files?.[0];
2086
2190
  if (!file) return;
@@ -2376,13 +2480,13 @@ function ChannelSettings({
2376
2480
  onClose,
2377
2481
  className
2378
2482
  }) {
2379
- const [editingName, setEditingName] = react.useState(false);
2380
- const [nameValue, setNameValue] = react.useState(channelName);
2381
- const [showInvite, setShowInvite] = react.useState(false);
2382
- const [inviteId, setInviteId] = react.useState("");
2383
- const [inviteName, setInviteName] = react.useState("");
2384
- const [inviteRole, setInviteRole] = react.useState("radiologist");
2385
- 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);
2386
2490
  const handleSaveName = async () => {
2387
2491
  if (nameValue.trim() && nameValue !== channelName) {
2388
2492
  await onUpdateChannel({ name: nameValue.trim() });
@@ -2673,7 +2777,7 @@ function PinnedMessagesBar({
2673
2777
  onUnpin,
2674
2778
  className
2675
2779
  }) {
2676
- const [index, setIndex] = react.useState(0);
2780
+ const [index, setIndex] = React4.useState(0);
2677
2781
  if (pinnedMessages.length === 0) return null;
2678
2782
  const safeIndex = Math.min(index, pinnedMessages.length - 1);
2679
2783
  const current = pinnedMessages[safeIndex];
@@ -2832,8 +2936,8 @@ function CollabPanel({
2832
2936
  style
2833
2937
  }) {
2834
2938
  const { config } = useCollab();
2835
- const [showSettings, setShowSettings] = react.useState(false);
2836
- const messageListRef = react.useRef(null);
2939
+ const [showSettings, setShowSettings] = React4.useState(false);
2940
+ const messageListRef = React4.useRef(null);
2837
2941
  const {
2838
2942
  conversation,
2839
2943
  messages,
@@ -3005,17 +3109,17 @@ function CollabPopup({
3005
3109
  height = 520,
3006
3110
  className
3007
3111
  }) {
3008
- const [position, setPosition] = react.useState(
3112
+ const [position, setPosition] = React4.useState(
3009
3113
  initialPosition || {
3010
3114
  x: window.innerWidth - width - 24,
3011
3115
  y: Math.max(24, window.innerHeight - height - 104)
3012
3116
  }
3013
3117
  );
3014
- const [isDragging, setIsDragging] = react.useState(false);
3015
- const [isMinimized, setIsMinimized] = react.useState(false);
3016
- const dragOffset = react.useRef({ x: 0, y: 0 });
3017
- const containerRef = react.useRef(null);
3018
- 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(
3019
3123
  (e) => {
3020
3124
  if (!e.target.closest("[data-drag-handle]")) return;
3021
3125
  setIsDragging(true);
@@ -3027,7 +3131,7 @@ function CollabPopup({
3027
3131
  },
3028
3132
  [position]
3029
3133
  );
3030
- react.useEffect(() => {
3134
+ React4.useEffect(() => {
3031
3135
  if (!isOpen) return;
3032
3136
  const handleKey = (e) => {
3033
3137
  if (e.key === "Escape") {
@@ -3037,7 +3141,7 @@ function CollabPopup({
3037
3141
  window.addEventListener("keydown", handleKey);
3038
3142
  return () => window.removeEventListener("keydown", handleKey);
3039
3143
  }, [isOpen, onClose]);
3040
- react.useEffect(() => {
3144
+ React4.useEffect(() => {
3041
3145
  if (!isDragging) return;
3042
3146
  const handleMouseMove = (e) => {
3043
3147
  setPosition({
@@ -3160,25 +3264,25 @@ function useInlineCollab({
3160
3264
  invalidatePreview,
3161
3265
  createConversationWithMessage
3162
3266
  } = useCollab();
3163
- const [preview, setPreview] = react.useState(null);
3164
- const [messages, setMessages] = react.useState([]);
3165
- const [participants, setParticipants] = react.useState([]);
3166
- const [unreadCount, setUnreadCount] = react.useState(0);
3167
- const [isLoading, setIsLoading] = react.useState(true);
3168
- const [isSubscribed, setIsSubscribed] = react.useState(false);
3169
- const [error, setError] = react.useState(null);
3170
- const elementRef = react.useRef(null);
3171
- const observerRef = react.useRef(null);
3172
- const subscribedConversationIdRef = react.useRef(null);
3173
- 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(
3174
3278
  (msgs) => msgs.length > messageLimit ? msgs.slice(msgs.length - messageLimit) : msgs,
3175
3279
  [messageLimit]
3176
3280
  );
3177
- const buildName = react.useCallback(
3281
+ const buildName = React4.useCallback(
3178
3282
  () => buildChannelName(patientData),
3179
3283
  [patientData]
3180
3284
  );
3181
- react.useEffect(() => {
3285
+ React4.useEffect(() => {
3182
3286
  let cancelled = false;
3183
3287
  const load = async () => {
3184
3288
  setIsLoading(true);
@@ -3204,7 +3308,7 @@ function useInlineCollab({
3204
3308
  cancelled = true;
3205
3309
  };
3206
3310
  }, [orderId, requestPreview, trimToLimit]);
3207
- const subscribe = react.useCallback(
3311
+ const subscribe = React4.useCallback(
3208
3312
  (conversationId) => {
3209
3313
  if (subscribedConversationIdRef.current === conversationId) return;
3210
3314
  socket.joinConversation(conversationId, {
@@ -3228,7 +3332,7 @@ function useInlineCollab({
3228
3332
  },
3229
3333
  [socket, trimToLimit, config.userId]
3230
3334
  );
3231
- const unsubscribe = react.useCallback(() => {
3335
+ const unsubscribe = React4.useCallback(() => {
3232
3336
  const convId = subscribedConversationIdRef.current;
3233
3337
  if (convId) {
3234
3338
  socket.leaveConversation(convId);
@@ -3236,7 +3340,7 @@ function useInlineCollab({
3236
3340
  setIsSubscribed(false);
3237
3341
  }
3238
3342
  }, [socket]);
3239
- const containerRef = react.useCallback(
3343
+ const containerRef = React4.useCallback(
3240
3344
  (element) => {
3241
3345
  if (observerRef.current) {
3242
3346
  observerRef.current.disconnect();
@@ -3262,18 +3366,18 @@ function useInlineCollab({
3262
3366
  },
3263
3367
  [preview, subscribe, unsubscribe]
3264
3368
  );
3265
- react.useEffect(() => {
3369
+ React4.useEffect(() => {
3266
3370
  return () => {
3267
3371
  if (observerRef.current) observerRef.current.disconnect();
3268
3372
  unsubscribe();
3269
3373
  };
3270
3374
  }, [unsubscribe]);
3271
- react.useEffect(() => {
3375
+ React4.useEffect(() => {
3272
3376
  if (preview?.conversationId && elementRef.current && !observerRef.current) {
3273
3377
  containerRef(elementRef.current);
3274
3378
  }
3275
3379
  }, [preview, containerRef]);
3276
- const dispatchSend = react.useCallback(
3380
+ const dispatchSend = React4.useCallback(
3277
3381
  async (payload) => {
3278
3382
  if (!preview) {
3279
3383
  try {
@@ -3330,7 +3434,7 @@ function useInlineCollab({
3330
3434
  config
3331
3435
  ]
3332
3436
  );
3333
- const sendMessage = react.useCallback(
3437
+ const sendMessage = React4.useCallback(
3334
3438
  async (body) => {
3335
3439
  const trimmed = body.trim();
3336
3440
  if (!trimmed) return;
@@ -3338,7 +3442,7 @@ function useInlineCollab({
3338
3442
  },
3339
3443
  [dispatchSend]
3340
3444
  );
3341
- const sendAudioMessage = react.useCallback(
3445
+ const sendAudioMessage = React4.useCallback(
3342
3446
  async (audioBlob, duration) => {
3343
3447
  if (!config.onUploadFile) {
3344
3448
  setError("File upload is not configured");
@@ -3451,10 +3555,10 @@ function RoleDot({ role }) {
3451
3555
  return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles13.roleDot, backgroundColor: colors[role] } });
3452
3556
  }
3453
3557
  function InlineAudioPlayer({ url, duration }) {
3454
- const audioRef = react.useRef(null);
3455
- const [isPlaying, setIsPlaying] = react.useState(false);
3456
- const [currentTime, setCurrentTime] = react.useState(0);
3457
- 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(
3458
3562
  (e) => {
3459
3563
  e.stopPropagation();
3460
3564
  const audio = audioRef.current;
@@ -3468,7 +3572,7 @@ function InlineAudioPlayer({ url, duration }) {
3468
3572
  },
3469
3573
  [isPlaying]
3470
3574
  );
3471
- react.useEffect(() => {
3575
+ React4.useEffect(() => {
3472
3576
  const audio = audioRef.current;
3473
3577
  if (!audio) return;
3474
3578
  const onPlay = () => setIsPlaying(true);
@@ -3544,10 +3648,10 @@ function InlineInputBar({
3544
3648
  showExpand,
3545
3649
  onExpand
3546
3650
  }) {
3547
- const [text, setText] = react.useState("");
3548
- const [isSending, setIsSending] = react.useState(false);
3549
- const inputRef = react.useRef(null);
3550
- 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 () => {
3551
3655
  const trimmed = text.trim();
3552
3656
  if (!trimmed || isSending) return;
3553
3657
  setIsSending(true);
@@ -3559,7 +3663,7 @@ function InlineInputBar({
3559
3663
  setIsSending(false);
3560
3664
  }
3561
3665
  }, [text, isSending, onSend]);
3562
- const handleKeyDown = react.useCallback(
3666
+ const handleKeyDown = React4.useCallback(
3563
3667
  (e) => {
3564
3668
  if (e.key === "Enter" && !e.shiftKey) {
3565
3669
  e.preventDefault();
@@ -3792,10 +3896,10 @@ var styles13 = {
3792
3896
  function useConversationList(options) {
3793
3897
  const enabled = options?.enabled ?? true;
3794
3898
  const { fetchConversationList, config, totalUnread: serverTotalUnread } = useCollab();
3795
- const [conversations, setConversations] = react.useState([]);
3796
- const [isLoading, setIsLoading] = react.useState(enabled);
3797
- const [error, setError] = react.useState(null);
3798
- 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 () => {
3799
3903
  try {
3800
3904
  const list = await fetchConversationList();
3801
3905
  setConversations(list);
@@ -3807,7 +3911,7 @@ function useConversationList(options) {
3807
3911
  });
3808
3912
  }
3809
3913
  }, [fetchConversationList, config]);
3810
- const refresh = react.useCallback(async () => {
3914
+ const refresh = React4.useCallback(async () => {
3811
3915
  setIsLoading(true);
3812
3916
  setError(null);
3813
3917
  try {
@@ -3825,12 +3929,12 @@ function useConversationList(options) {
3825
3929
  setIsLoading(false);
3826
3930
  }
3827
3931
  }, [fetchConversationList, config]);
3828
- react.useEffect(() => {
3932
+ React4.useEffect(() => {
3829
3933
  if (!enabled) return;
3830
3934
  refresh();
3831
3935
  }, [enabled, refresh]);
3832
- const hasHandledInitialUnreadRef = react.useRef(false);
3833
- react.useEffect(() => {
3936
+ const hasHandledInitialUnreadRef = React4.useRef(false);
3937
+ React4.useEffect(() => {
3834
3938
  if (!enabled) return;
3835
3939
  if (!hasHandledInitialUnreadRef.current) {
3836
3940
  hasHandledInitialUnreadRef.current = true;
@@ -3838,7 +3942,7 @@ function useConversationList(options) {
3838
3942
  }
3839
3943
  silentRefresh();
3840
3944
  }, [serverTotalUnread, enabled]);
3841
- const filter = react.useCallback(
3945
+ const filter = React4.useCallback(
3842
3946
  (query) => {
3843
3947
  const q = query.trim().toLowerCase();
3844
3948
  if (!q) return conversations;
@@ -3903,7 +4007,7 @@ function ConversationListItem({
3903
4007
  ...styles14.time,
3904
4008
  ...hasUnread ? styles14.timeUnread : {}
3905
4009
  },
3906
- children: formatRelativeTime(item.lastActivityAt)
4010
+ children: formatInboxTimestamp(item.lastActivityAt)
3907
4011
  }
3908
4012
  )
3909
4013
  ] }),
@@ -3946,23 +4050,6 @@ function renderLastMessagePreview(message) {
3946
4050
  }
3947
4051
  }
3948
4052
  }
3949
- function formatRelativeTime(iso) {
3950
- try {
3951
- const date = new Date(iso);
3952
- const now = /* @__PURE__ */ new Date();
3953
- const diffMs = now.getTime() - date.getTime();
3954
- const diffMin = Math.floor(diffMs / 6e4);
3955
- const diffHr = Math.floor(diffMs / 36e5);
3956
- const diffDay = Math.floor(diffMs / 864e5);
3957
- if (diffMin < 1) return "now";
3958
- if (diffMin < 60) return `${diffMin}m`;
3959
- if (diffHr < 24) return `${diffHr}h`;
3960
- if (diffDay < 7) return `${diffDay}d`;
3961
- return date.toLocaleDateString([], { month: "short", day: "numeric" });
3962
- } catch {
3963
- return "";
3964
- }
3965
- }
3966
4053
  var styles14 = {
3967
4054
  container: {
3968
4055
  display: "flex",
@@ -4092,9 +4179,9 @@ function ConversationList({
4092
4179
  title = "Conversations",
4093
4180
  className
4094
4181
  }) {
4095
- const [query, setQuery] = react.useState("");
4096
- const [showUnreadOnly, setShowUnreadOnly] = react.useState(false);
4097
- const filtered = react.useMemo(() => {
4182
+ const [query, setQuery] = React4.useState("");
4183
+ const [showUnreadOnly, setShowUnreadOnly] = React4.useState(false);
4184
+ const filtered = React4.useMemo(() => {
4098
4185
  let result = conversations;
4099
4186
  if (showUnreadOnly) {
4100
4187
  result = result.filter((c) => c.unreadCount > 0);
@@ -4271,7 +4358,7 @@ function CollabInbox({
4271
4358
  style
4272
4359
  }) {
4273
4360
  const { conversations, isLoading, error } = useConversationList();
4274
- const [selectedId, setSelectedId] = react.useState(initialConversationId ?? null);
4361
+ const [selectedId, setSelectedId] = React4.useState(initialConversationId ?? null);
4275
4362
  const selected = conversations.find((c) => c.id === selectedId) || null;
4276
4363
  const handleSelect = (item) => {
4277
4364
  setSelectedId(item.id);
@@ -4373,12 +4460,12 @@ function useMessages({
4373
4460
  initialLimit = MESSAGES_PAGE_SIZE
4374
4461
  }) {
4375
4462
  const { fetchMessages, config } = useCollab();
4376
- const [messages, setMessages] = react.useState([]);
4377
- const [isLoading, setIsLoading] = react.useState(false);
4378
- const [hasMore, setHasMore] = react.useState(true);
4379
- const [error, setError] = react.useState(null);
4380
- const loadedRef = react.useRef(null);
4381
- 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(() => {
4382
4469
  if (!conversationId) {
4383
4470
  setMessages([]);
4384
4471
  setHasMore(false);
@@ -4414,7 +4501,7 @@ function useMessages({
4414
4501
  cancelled = true;
4415
4502
  };
4416
4503
  }, [conversationId, initialLimit, fetchMessages, config]);
4417
- const loadMore = react.useCallback(async () => {
4504
+ const loadMore = React4.useCallback(async () => {
4418
4505
  if (!conversationId || !hasMore || isLoading) return;
4419
4506
  const oldestId = messages[0]?.id;
4420
4507
  setIsLoading(true);
@@ -4435,17 +4522,17 @@ function useMessages({
4435
4522
  setIsLoading(false);
4436
4523
  }
4437
4524
  }, [conversationId, hasMore, isLoading, messages, initialLimit, fetchMessages, config]);
4438
- const addMessage = react.useCallback((message) => {
4525
+ const addMessage = React4.useCallback((message) => {
4439
4526
  setMessages((prev) => [...prev, message]);
4440
4527
  }, []);
4441
- const updateMessageReadBy = react.useCallback((messageId, userId) => {
4528
+ const updateMessageReadBy = React4.useCallback((messageId, userId) => {
4442
4529
  setMessages(
4443
4530
  (prev) => prev.map(
4444
4531
  (msg) => msg.id === messageId && !(msg.readBy ?? []).includes(userId) ? { ...msg, readBy: [...msg.readBy ?? [], userId] } : msg
4445
4532
  )
4446
4533
  );
4447
4534
  }, []);
4448
- const reset = react.useCallback(() => {
4535
+ const reset = React4.useCallback(() => {
4449
4536
  setMessages([]);
4450
4537
  setHasMore(true);
4451
4538
  setError(null);
@@ -4463,17 +4550,17 @@ function useMessages({
4463
4550
  };
4464
4551
  }
4465
4552
  function useAudioRecorder() {
4466
- const [isRecording, setIsRecording] = react.useState(false);
4467
- const [duration, setDuration] = react.useState(0);
4468
- const [error, setError] = react.useState(null);
4469
- const mediaRecorderRef = react.useRef(null);
4470
- const chunksRef = react.useRef([]);
4471
- const streamRef = react.useRef(null);
4472
- const timerRef = react.useRef(null);
4473
- const startTimeRef = react.useRef(0);
4474
- const resolveRef = react.useRef(null);
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);
4475
4562
  const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && !!window.MediaRecorder;
4476
- const cleanup = react.useCallback(() => {
4563
+ const cleanup = React4.useCallback(() => {
4477
4564
  if (timerRef.current) {
4478
4565
  clearInterval(timerRef.current);
4479
4566
  timerRef.current = null;
@@ -4487,7 +4574,7 @@ function useAudioRecorder() {
4487
4574
  setIsRecording(false);
4488
4575
  setDuration(0);
4489
4576
  }, []);
4490
- const start = react.useCallback(async () => {
4577
+ const start = React4.useCallback(async () => {
4491
4578
  if (!isSupported) {
4492
4579
  setError("Audio recording is not supported in this browser");
4493
4580
  return;
@@ -4534,7 +4621,7 @@ function useAudioRecorder() {
4534
4621
  cleanup();
4535
4622
  }
4536
4623
  }, [isSupported, cleanup]);
4537
- const stop = react.useCallback(async () => {
4624
+ const stop = React4.useCallback(async () => {
4538
4625
  return new Promise((resolve) => {
4539
4626
  if (!mediaRecorderRef.current || mediaRecorderRef.current.state === "inactive") {
4540
4627
  resolve(null);
@@ -4544,7 +4631,7 @@ function useAudioRecorder() {
4544
4631
  mediaRecorderRef.current.stop();
4545
4632
  });
4546
4633
  }, []);
4547
- const cancel = react.useCallback(() => {
4634
+ const cancel = React4.useCallback(() => {
4548
4635
  if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
4549
4636
  mediaRecorderRef.current.onstop = null;
4550
4637
  mediaRecorderRef.current.stop();
@@ -4565,19 +4652,19 @@ function useAudioRecorder() {
4565
4652
  }
4566
4653
  function useUnreadCount() {
4567
4654
  const { socket, totalUnread } = useCollab();
4568
- const [counts, setCounts] = react.useState({});
4569
- react.useEffect(() => {
4655
+ const [counts, setCounts] = React4.useState({});
4656
+ React4.useEffect(() => {
4570
4657
  socket.onUnreadCountUpdate((serverCounts) => {
4571
4658
  setCounts(serverCounts);
4572
4659
  });
4573
4660
  }, [socket]);
4574
- const getCountForConversation = react.useCallback(
4661
+ const getCountForConversation = React4.useCallback(
4575
4662
  (conversationId) => {
4576
4663
  return counts[conversationId] ?? 0;
4577
4664
  },
4578
4665
  [counts]
4579
4666
  );
4580
- const clearForConversation = react.useCallback((conversationId) => {
4667
+ const clearForConversation = React4.useCallback((conversationId) => {
4581
4668
  setCounts((prev) => {
4582
4669
  const next = { ...prev };
4583
4670
  delete next[conversationId];
@@ -4595,10 +4682,10 @@ function usePinnedMessages({
4595
4682
  initial = []
4596
4683
  }) {
4597
4684
  const { pinMessage, unpinMessage, fetchPinnedMessages, config } = useCollab();
4598
- const [pinnedMessages, setPinnedMessages] = react.useState(initial);
4599
- const [isLoading, setIsLoading] = react.useState(false);
4600
- const [error, setError] = react.useState(null);
4601
- 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 () => {
4602
4689
  if (!conversationId) return;
4603
4690
  setIsLoading(true);
4604
4691
  setError(null);
@@ -4612,12 +4699,12 @@ function usePinnedMessages({
4612
4699
  setIsLoading(false);
4613
4700
  }
4614
4701
  }, [conversationId, fetchPinnedMessages]);
4615
- react.useEffect(() => {
4702
+ React4.useEffect(() => {
4616
4703
  if (conversationId && initial.length === 0) {
4617
4704
  refresh();
4618
4705
  }
4619
4706
  }, [conversationId]);
4620
- const pin = react.useCallback(
4707
+ const pin = React4.useCallback(
4621
4708
  async (messageId) => {
4622
4709
  if (!conversationId) return;
4623
4710
  setError(null);
@@ -4635,7 +4722,7 @@ function usePinnedMessages({
4635
4722
  },
4636
4723
  [conversationId, pinMessage, config]
4637
4724
  );
4638
- const unpin = react.useCallback(
4725
+ const unpin = React4.useCallback(
4639
4726
  async (messageId) => {
4640
4727
  if (!conversationId) return;
4641
4728
  setError(null);
@@ -4653,13 +4740,13 @@ function usePinnedMessages({
4653
4740
  },
4654
4741
  [conversationId, unpinMessage, config]
4655
4742
  );
4656
- const handlePinned = react.useCallback((message) => {
4743
+ const handlePinned = React4.useCallback((message) => {
4657
4744
  setPinnedMessages((prev) => {
4658
4745
  if (prev.some((m) => m.id === message.id)) return prev;
4659
4746
  return [...prev, message];
4660
4747
  });
4661
4748
  }, []);
4662
- const handleUnpinned = react.useCallback((messageId) => {
4749
+ const handleUnpinned = React4.useCallback((messageId) => {
4663
4750
  setPinnedMessages((prev) => prev.filter((m) => m.id !== messageId));
4664
4751
  }, []);
4665
4752
  return {