@natoe/colab 0.1.3 → 0.1.5

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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import React4, { createContext, forwardRef, useRef, useImperativeHandle, useEffect, useCallback, useState, useContext, useMemo } from 'react';
1
+ import React4, { createContext, forwardRef, useRef, useImperativeHandle, useEffect, useCallback, useState, useContext, useId, useMemo } from 'react';
2
2
  import { Socket, Presence } from 'phoenix';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
 
@@ -828,6 +828,7 @@ function useConversation({
828
828
  body,
829
829
  type: "text",
830
830
  insertedAt: (/* @__PURE__ */ new Date()).toISOString(),
831
+ status: "sending",
831
832
  ...replyTo ? {
832
833
  replyToId: replyTo.id,
833
834
  replyToSnapshot: {
@@ -843,7 +844,9 @@ function useConversation({
843
844
  try {
844
845
  await socket.sendMessage(conv.id, payload);
845
846
  } catch (err) {
846
- setMessages((prev) => prev.filter((m) => m.id !== optimistic.id));
847
+ setMessages(
848
+ (prev) => prev.map((m) => m.id === optimistic.id ? { ...m, status: "failed" } : m)
849
+ );
847
850
  config.onError?.({
848
851
  code: "SEND_FAILED",
849
852
  message: "Failed to send message",
@@ -853,6 +856,34 @@ function useConversation({
853
856
  },
854
857
  [ensureConversation, socket, replyTo, config]
855
858
  );
859
+ const retryMessage = useCallback(
860
+ async (messageId) => {
861
+ if (!conversation) return;
862
+ const target = messages.find((m) => m.id === messageId);
863
+ if (!target || target.status !== "failed" || target.type !== "text") return;
864
+ setMessages(
865
+ (prev) => prev.map((m) => m.id === messageId ? { ...m, status: "sending" } : m)
866
+ );
867
+ const payload = {
868
+ body: target.body,
869
+ type: "text",
870
+ ...target.replyToId ? { replyToId: target.replyToId } : {}
871
+ };
872
+ try {
873
+ await socket.sendMessage(conversation.id, payload);
874
+ } catch (err) {
875
+ setMessages(
876
+ (prev) => prev.map((m) => m.id === messageId ? { ...m, status: "failed" } : m)
877
+ );
878
+ config.onError?.({
879
+ code: "SEND_FAILED",
880
+ message: "Failed to send message",
881
+ details: err
882
+ });
883
+ }
884
+ },
885
+ [conversation, messages, socket, config]
886
+ );
856
887
  const sendAudioMessage = useCallback(
857
888
  async (audioBlob, duration) => {
858
889
  if (!config.onUploadFile) return;
@@ -945,6 +976,7 @@ function useConversation({
945
976
  message: "Failed to pin message",
946
977
  details: err
947
978
  });
979
+ throw err;
948
980
  }
949
981
  },
950
982
  [conversation, pinMessageApi, config]
@@ -960,6 +992,7 @@ function useConversation({
960
992
  message: "Failed to unpin message",
961
993
  details: err
962
994
  });
995
+ throw err;
963
996
  }
964
997
  },
965
998
  [conversation, unpinMessageApi, config]
@@ -977,6 +1010,7 @@ function useConversation({
977
1010
  replyTo,
978
1011
  setReplyTo,
979
1012
  sendMessage,
1013
+ retryMessage,
980
1014
  sendAudioMessage,
981
1015
  sendFileMessage,
982
1016
  sendDeepLink,
@@ -1339,91 +1373,247 @@ var styles3 = {
1339
1373
  textDecoration: "underline"
1340
1374
  }
1341
1375
  };
1376
+ function ReplyIcon({ size = 18, color = "currentColor" }) {
1377
+ return /* @__PURE__ */ jsx(
1378
+ "svg",
1379
+ {
1380
+ xmlns: "http://www.w3.org/2000/svg",
1381
+ width: size,
1382
+ height: size,
1383
+ viewBox: "0 0 24 24",
1384
+ fill: color,
1385
+ "aria-hidden": "true",
1386
+ children: /* @__PURE__ */ jsx("path", { d: "M10 9V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z" })
1387
+ }
1388
+ );
1389
+ }
1390
+ function PinIcon({ size = 14, color = "currentColor" }) {
1391
+ return /* @__PURE__ */ jsx(
1392
+ "svg",
1393
+ {
1394
+ xmlns: "http://www.w3.org/2000/svg",
1395
+ width: size,
1396
+ height: size,
1397
+ viewBox: "0 0 24 24",
1398
+ fill: color,
1399
+ "aria-hidden": "true",
1400
+ children: /* @__PURE__ */ jsx("path", { d: "M16 9V4l1 0c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1l1 0v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z" })
1401
+ }
1402
+ );
1403
+ }
1404
+ function CopyIcon({ size = 18, color = "currentColor" }) {
1405
+ return /* @__PURE__ */ jsx(
1406
+ "svg",
1407
+ {
1408
+ xmlns: "http://www.w3.org/2000/svg",
1409
+ width: size,
1410
+ height: size,
1411
+ viewBox: "0 0 24 24",
1412
+ fill: color,
1413
+ "aria-hidden": "true",
1414
+ children: /* @__PURE__ */ jsx("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" })
1415
+ }
1416
+ );
1417
+ }
1418
+ function MoreIcon({ size = 18, color = "currentColor" }) {
1419
+ return /* @__PURE__ */ jsxs(
1420
+ "svg",
1421
+ {
1422
+ xmlns: "http://www.w3.org/2000/svg",
1423
+ width: size,
1424
+ height: size,
1425
+ viewBox: "0 0 24 24",
1426
+ fill: color,
1427
+ "aria-hidden": "true",
1428
+ children: [
1429
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "5", r: "2" }),
1430
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2" }),
1431
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "19", r: "2" })
1432
+ ]
1433
+ }
1434
+ );
1435
+ }
1342
1436
  function MessageActionsMenu({
1343
1437
  message,
1344
1438
  onReply,
1345
1439
  onPin,
1346
1440
  onUnpin,
1347
1441
  onCopy,
1348
- visible,
1349
1442
  pinDisabled = false,
1443
+ anchorRight = false,
1350
1444
  className
1351
1445
  }) {
1352
- if (!visible) return null;
1446
+ const [isOpen, setIsOpen] = useState(false);
1447
+ const wrapperRef = useRef(null);
1448
+ const triggerRef = useRef(null);
1353
1449
  const isPinned = !!message.isPinned;
1354
1450
  const canCopy = message.type === "text" && !!message.body;
1355
- const handleCopy = () => {
1356
- if (!onCopy) return;
1357
- onCopy(message.body);
1451
+ useEffect(() => {
1452
+ if (!isOpen) return;
1453
+ const handleClick = (e) => {
1454
+ if (!wrapperRef.current?.contains(e.target)) {
1455
+ setIsOpen(false);
1456
+ }
1457
+ };
1458
+ const handleKey = (e) => {
1459
+ if (e.key === "Escape") {
1460
+ setIsOpen(false);
1461
+ triggerRef.current?.focus();
1462
+ }
1463
+ };
1464
+ document.addEventListener("mousedown", handleClick);
1465
+ document.addEventListener("keydown", handleKey);
1466
+ return () => {
1467
+ document.removeEventListener("mousedown", handleClick);
1468
+ document.removeEventListener("keydown", handleKey);
1469
+ };
1470
+ }, [isOpen]);
1471
+ const run = (fn) => {
1472
+ setIsOpen(false);
1473
+ fn();
1358
1474
  };
1359
- return /* @__PURE__ */ jsxs("div", { className, style: styles4.container, children: [
1475
+ return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className, style: styles4.wrapper, children: [
1360
1476
  /* @__PURE__ */ jsx(
1361
1477
  "button",
1362
1478
  {
1363
- onClick: () => onReply(message),
1364
- style: styles4.button,
1365
- title: "Reply",
1479
+ ref: triggerRef,
1366
1480
  type: "button",
1367
- children: "\u21A9"
1481
+ onClick: (e) => {
1482
+ e.stopPropagation();
1483
+ setIsOpen((v) => !v);
1484
+ },
1485
+ "aria-label": "Message actions",
1486
+ "aria-haspopup": "menu",
1487
+ "aria-expanded": isOpen ? "true" : "false",
1488
+ title: "Message actions",
1489
+ style: styles4.trigger,
1490
+ children: /* @__PURE__ */ jsx(MoreIcon, { size: 18, color: "#4b5563" })
1368
1491
  }
1369
1492
  ),
1370
- isPinned ? /* @__PURE__ */ jsx(
1371
- "button",
1372
- {
1373
- onClick: () => onUnpin(message.id),
1374
- style: styles4.button,
1375
- title: "Unpin",
1376
- type: "button",
1377
- children: "\u{1F4CC}"
1378
- }
1379
- ) : /* @__PURE__ */ jsx(
1380
- "button",
1493
+ isOpen && /* @__PURE__ */ jsxs(
1494
+ "div",
1381
1495
  {
1382
- onClick: () => onPin(message.id),
1496
+ role: "menu",
1497
+ "aria-label": "Message actions",
1383
1498
  style: {
1384
- ...styles4.button,
1385
- opacity: pinDisabled ? 0.4 : 1,
1386
- cursor: pinDisabled ? "not-allowed" : "pointer"
1499
+ ...styles4.menu,
1500
+ ...anchorRight ? styles4.menuRight : styles4.menuLeft
1387
1501
  },
1388
- disabled: pinDisabled,
1389
- title: pinDisabled ? "Pin limit reached (max 3)" : "Pin",
1390
- type: "button",
1391
- children: "\u{1F4CC}"
1392
- }
1393
- ),
1394
- canCopy && onCopy && /* @__PURE__ */ jsx(
1395
- "button",
1396
- {
1397
- onClick: handleCopy,
1398
- style: styles4.button,
1399
- title: "Copy text",
1400
- type: "button",
1401
- children: "\u2398"
1502
+ children: [
1503
+ /* @__PURE__ */ jsxs(
1504
+ "button",
1505
+ {
1506
+ type: "button",
1507
+ role: "menuitem",
1508
+ onClick: () => run(() => onReply(message)),
1509
+ style: styles4.item,
1510
+ children: [
1511
+ /* @__PURE__ */ jsx(ReplyIcon, { size: 18, color: "#374151" }),
1512
+ /* @__PURE__ */ jsx("span", { children: "Reply" })
1513
+ ]
1514
+ }
1515
+ ),
1516
+ isPinned ? /* @__PURE__ */ jsxs(
1517
+ "button",
1518
+ {
1519
+ type: "button",
1520
+ role: "menuitem",
1521
+ onClick: () => run(() => onUnpin(message.id)),
1522
+ style: styles4.item,
1523
+ children: [
1524
+ /* @__PURE__ */ jsx(PinIcon, { size: 16, color: "#374151" }),
1525
+ /* @__PURE__ */ jsx("span", { children: "Unpin" })
1526
+ ]
1527
+ }
1528
+ ) : /* @__PURE__ */ jsxs(
1529
+ "button",
1530
+ {
1531
+ type: "button",
1532
+ role: "menuitem",
1533
+ onClick: () => run(() => onPin(message.id)),
1534
+ disabled: pinDisabled,
1535
+ style: {
1536
+ ...styles4.item,
1537
+ ...pinDisabled ? styles4.itemDisabled : {}
1538
+ },
1539
+ title: pinDisabled ? "Pin limit reached (max 3)" : void 0,
1540
+ children: [
1541
+ /* @__PURE__ */ jsx(PinIcon, { size: 16, color: "#374151" }),
1542
+ /* @__PURE__ */ jsx("span", { children: pinDisabled ? "Pin (limit reached)" : "Pin" })
1543
+ ]
1544
+ }
1545
+ ),
1546
+ canCopy && onCopy && /* @__PURE__ */ jsxs(
1547
+ "button",
1548
+ {
1549
+ type: "button",
1550
+ role: "menuitem",
1551
+ onClick: () => run(() => onCopy(message.body)),
1552
+ style: styles4.item,
1553
+ children: [
1554
+ /* @__PURE__ */ jsx(CopyIcon, { size: 16, color: "#374151" }),
1555
+ /* @__PURE__ */ jsx("span", { children: "Copy text" })
1556
+ ]
1557
+ }
1558
+ )
1559
+ ]
1402
1560
  }
1403
1561
  )
1404
1562
  ] });
1405
1563
  }
1406
1564
  var styles4 = {
1407
- container: {
1565
+ wrapper: {
1566
+ position: "relative",
1567
+ display: "inline-block"
1568
+ },
1569
+ trigger: {
1570
+ width: "36px",
1571
+ height: "36px",
1408
1572
  display: "flex",
1409
- gap: "2px",
1410
- padding: "2px",
1573
+ alignItems: "center",
1574
+ justifyContent: "center",
1575
+ backgroundColor: "transparent",
1576
+ border: "none",
1577
+ borderRadius: "50%",
1578
+ cursor: "pointer",
1579
+ color: "#4b5563"
1580
+ },
1581
+ menu: {
1582
+ position: "absolute",
1583
+ top: "100%",
1584
+ marginTop: "4px",
1585
+ minWidth: "160px",
1411
1586
  backgroundColor: "#ffffff",
1412
1587
  border: "1px solid #e5e7eb",
1413
- borderRadius: "20px",
1414
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.08)"
1588
+ borderRadius: "10px",
1589
+ boxShadow: "0 6px 18px rgba(0, 0, 0, 0.12)",
1590
+ padding: "4px",
1591
+ zIndex: 20
1415
1592
  },
1416
- button: {
1417
- width: "24px",
1418
- height: "24px",
1593
+ menuLeft: {
1594
+ left: 0
1595
+ },
1596
+ menuRight: {
1597
+ right: 0
1598
+ },
1599
+ item: {
1419
1600
  display: "flex",
1420
1601
  alignItems: "center",
1421
- justifyContent: "center",
1422
- fontSize: "12px",
1602
+ gap: "10px",
1603
+ width: "100%",
1604
+ minHeight: "40px",
1605
+ padding: "8px 12px",
1606
+ fontSize: "14px",
1607
+ color: "#111827",
1423
1608
  backgroundColor: "transparent",
1424
1609
  border: "none",
1425
- borderRadius: "50%",
1426
- cursor: "pointer"
1610
+ borderRadius: "6px",
1611
+ cursor: "pointer",
1612
+ textAlign: "left"
1613
+ },
1614
+ itemDisabled: {
1615
+ color: "#9ca3af",
1616
+ cursor: "not-allowed"
1427
1617
  }
1428
1618
  };
1429
1619
  function FileIcon({ size = 18, color = "currentColor" }) {
@@ -1440,7 +1630,7 @@ function FileIcon({ size = 18, color = "currentColor" }) {
1440
1630
  }
1441
1631
  );
1442
1632
  }
1443
- function PinIcon({ size = 14, color = "currentColor" }) {
1633
+ function AlertIcon({ size = 14, color = "currentColor" }) {
1444
1634
  return /* @__PURE__ */ jsx(
1445
1635
  "svg",
1446
1636
  {
@@ -1450,10 +1640,38 @@ function PinIcon({ size = 14, color = "currentColor" }) {
1450
1640
  viewBox: "0 0 24 24",
1451
1641
  fill: color,
1452
1642
  "aria-hidden": "true",
1453
- children: /* @__PURE__ */ jsx("path", { d: "M16 9V4l1 0c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1l1 0v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z" })
1643
+ children: /* @__PURE__ */ jsx("path", { d: "M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" })
1454
1644
  }
1455
1645
  );
1456
1646
  }
1647
+ function Spinner({ size = 16, color = "#6b7280", thickness = 2 }) {
1648
+ return /* @__PURE__ */ jsx(
1649
+ "span",
1650
+ {
1651
+ role: "status",
1652
+ "aria-label": "Loading",
1653
+ style: {
1654
+ display: "inline-block",
1655
+ width: size,
1656
+ height: size,
1657
+ border: `${thickness}px solid ${color}33`,
1658
+ borderTopColor: color,
1659
+ borderRadius: "50%",
1660
+ animation: "natoe-colab-spin 800ms linear infinite",
1661
+ verticalAlign: "middle"
1662
+ }
1663
+ }
1664
+ );
1665
+ }
1666
+ if (typeof document !== "undefined") {
1667
+ const existing = document.getElementById("natoe-colab-spin-keyframes");
1668
+ if (!existing) {
1669
+ const style = document.createElement("style");
1670
+ style.id = "natoe-colab-spin-keyframes";
1671
+ style.textContent = `@keyframes natoe-colab-spin { to { transform: rotate(360deg); } }`;
1672
+ document.head.appendChild(style);
1673
+ }
1674
+ }
1457
1675
  var ROLE_LABELS = {
1458
1676
  radiologist: "Radiologist",
1459
1677
  lab: "Imaging Center",
@@ -1472,15 +1690,30 @@ function MessageBubble({
1472
1690
  onUnpin,
1473
1691
  onCopy,
1474
1692
  onReplyJumpTo,
1693
+ onRetry,
1475
1694
  pinDisabled = false,
1476
1695
  className
1477
1696
  }) {
1478
- const [isHovered, setIsHovered] = useState(false);
1479
1697
  if (message.type === "system") {
1480
1698
  return /* @__PURE__ */ jsx(SystemBubble, { message });
1481
1699
  }
1482
1700
  const hasActions = !!(onReply || onPin || onUnpin || onCopy);
1483
- return /* @__PURE__ */ jsx(
1701
+ const actionsSlot = hasActions ? /* @__PURE__ */ jsx("div", { style: styles5.actionsSlot, children: /* @__PURE__ */ jsx(
1702
+ MessageActionsMenu,
1703
+ {
1704
+ message,
1705
+ onReply: onReply ?? (() => {
1706
+ }),
1707
+ onPin: onPin ?? (() => {
1708
+ }),
1709
+ onUnpin: onUnpin ?? (() => {
1710
+ }),
1711
+ onCopy,
1712
+ pinDisabled,
1713
+ anchorRight: !isOwn
1714
+ }
1715
+ ) }) : null;
1716
+ return /* @__PURE__ */ jsxs(
1484
1717
  "div",
1485
1718
  {
1486
1719
  className,
@@ -1488,10 +1721,9 @@ function MessageBubble({
1488
1721
  ...styles5.wrapper,
1489
1722
  justifyContent: isOwn ? "flex-end" : "flex-start"
1490
1723
  },
1491
- onMouseEnter: () => setIsHovered(true),
1492
- onMouseLeave: () => setIsHovered(false),
1493
- children: /* @__PURE__ */ jsxs("div", { style: styles5.bubbleWrapper, children: [
1494
- /* @__PURE__ */ jsxs(
1724
+ children: [
1725
+ isOwn && actionsSlot,
1726
+ /* @__PURE__ */ jsx("div", { style: styles5.bubbleWrapper, children: /* @__PURE__ */ jsxs(
1495
1727
  "div",
1496
1728
  {
1497
1729
  style: {
@@ -1521,7 +1753,25 @@ function MessageBubble({
1521
1753
  message.type === "file" && /* @__PURE__ */ jsx(FileContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
1522
1754
  message.type === "deep_link" && /* @__PURE__ */ jsx(DeepLinkContent, { body: message.body, metadata: message.metadata, onDeepLinkClick }),
1523
1755
  /* @__PURE__ */ jsx("div", { style: styles5.timestamp, children: formatTime(message.insertedAt) }),
1524
- showSeenBy && isOwn && /* @__PURE__ */ jsx(
1756
+ isOwn && message.status === "sending" && /* @__PURE__ */ jsxs("div", { style: styles5.statusRow, children: [
1757
+ /* @__PURE__ */ jsx(Spinner, { size: 12, color: "rgba(255,255,255,0.85)" }),
1758
+ /* @__PURE__ */ jsx("span", { style: styles5.statusText, children: "Sending\u2026" })
1759
+ ] }),
1760
+ isOwn && message.status === "failed" && /* @__PURE__ */ jsxs("div", { style: styles5.statusRow, children: [
1761
+ /* @__PURE__ */ jsx(AlertIcon, { size: 12, color: "#fecaca" }),
1762
+ /* @__PURE__ */ jsx("span", { style: styles5.statusText, children: "Not sent" }),
1763
+ onRetry && /* @__PURE__ */ jsx(
1764
+ "button",
1765
+ {
1766
+ type: "button",
1767
+ onClick: () => onRetry(message.id),
1768
+ style: styles5.retryButton,
1769
+ "aria-label": "Retry sending message",
1770
+ children: "Retry"
1771
+ }
1772
+ )
1773
+ ] }),
1774
+ showSeenBy && isOwn && !message.status && /* @__PURE__ */ jsx(
1525
1775
  SeenByIndicator,
1526
1776
  {
1527
1777
  readBy: message.readBy ?? [],
@@ -1532,33 +1782,9 @@ function MessageBubble({
1532
1782
  )
1533
1783
  ]
1534
1784
  }
1535
- ),
1536
- hasActions && /* @__PURE__ */ jsx(
1537
- "div",
1538
- {
1539
- style: {
1540
- ...styles5.actionsWrapper,
1541
- [isOwn ? "right" : "left"]: "100%",
1542
- [isOwn ? "marginRight" : "marginLeft"]: "6px"
1543
- },
1544
- children: /* @__PURE__ */ jsx(
1545
- MessageActionsMenu,
1546
- {
1547
- message,
1548
- onReply: onReply ?? (() => {
1549
- }),
1550
- onPin: onPin ?? (() => {
1551
- }),
1552
- onUnpin: onUnpin ?? (() => {
1553
- }),
1554
- onCopy,
1555
- visible: isHovered,
1556
- pinDisabled
1557
- }
1558
- )
1559
- }
1560
- )
1561
- ] })
1785
+ ) }),
1786
+ !isOwn && actionsSlot
1787
+ ]
1562
1788
  }
1563
1789
  );
1564
1790
  }
@@ -1655,6 +1881,8 @@ function escapeRegex2(string) {
1655
1881
  var styles5 = {
1656
1882
  wrapper: {
1657
1883
  display: "flex",
1884
+ alignItems: "flex-end",
1885
+ gap: "4px",
1658
1886
  marginBottom: "8px",
1659
1887
  paddingLeft: "16px",
1660
1888
  paddingRight: "16px"
@@ -1663,6 +1891,10 @@ var styles5 = {
1663
1891
  position: "relative",
1664
1892
  maxWidth: "75%"
1665
1893
  },
1894
+ actionsSlot: {
1895
+ flexShrink: 0,
1896
+ alignSelf: "flex-end"
1897
+ },
1666
1898
  bubble: {
1667
1899
  padding: "10px 14px",
1668
1900
  borderRadius: "12px",
@@ -1716,11 +1948,27 @@ var styles5 = {
1716
1948
  marginTop: "4px",
1717
1949
  textAlign: "right"
1718
1950
  },
1719
- actionsWrapper: {
1720
- position: "absolute",
1721
- top: "50%",
1722
- transform: "translateY(-50%)",
1723
- zIndex: 10
1951
+ statusRow: {
1952
+ display: "flex",
1953
+ alignItems: "center",
1954
+ gap: "6px",
1955
+ marginTop: "4px",
1956
+ fontSize: "12px",
1957
+ justifyContent: "flex-end",
1958
+ color: "rgba(255, 255, 255, 0.9)"
1959
+ },
1960
+ statusText: {
1961
+ opacity: 0.9
1962
+ },
1963
+ retryButton: {
1964
+ background: "transparent",
1965
+ border: "none",
1966
+ color: "#ffffff",
1967
+ fontWeight: 600,
1968
+ fontSize: "12px",
1969
+ textDecoration: "underline",
1970
+ cursor: "pointer",
1971
+ padding: "0 2px"
1724
1972
  },
1725
1973
  audioContainer: {
1726
1974
  display: "flex",
@@ -1794,6 +2042,44 @@ var styles5 = {
1794
2042
  color: "#9ca3af"
1795
2043
  }
1796
2044
  };
2045
+ function ChatIllustration({ size = 96 }) {
2046
+ return /* @__PURE__ */ jsxs(
2047
+ "svg",
2048
+ {
2049
+ xmlns: "http://www.w3.org/2000/svg",
2050
+ width: size,
2051
+ height: size,
2052
+ viewBox: "0 0 120 120",
2053
+ fill: "none",
2054
+ "aria-hidden": "true",
2055
+ children: [
2056
+ /* @__PURE__ */ jsx("circle", { cx: "60", cy: "60", r: "56", fill: "#eff6ff" }),
2057
+ /* @__PURE__ */ jsx(
2058
+ "path",
2059
+ {
2060
+ d: "M28 44a10 10 0 0 1 10-10h28a10 10 0 0 1 10 10v18a10 10 0 0 1-10 10H46l-10 8v-8h2a10 10 0 0 1-10-10V44Z",
2061
+ fill: "#ffffff",
2062
+ stroke: "#2563eb",
2063
+ strokeWidth: "2.5"
2064
+ }
2065
+ ),
2066
+ /* @__PURE__ */ jsx(
2067
+ "path",
2068
+ {
2069
+ d: "M52 60a8 8 0 0 1 8-8h26a8 8 0 0 1 8 8v14a8 8 0 0 1-8 8H72l8 8v-8h6a8 8 0 0 0 0-30Z",
2070
+ fill: "#dbeafe",
2071
+ stroke: "#2563eb",
2072
+ strokeWidth: "2.5"
2073
+ }
2074
+ ),
2075
+ /* @__PURE__ */ jsx("circle", { cx: "48", cy: "53", r: "2.5", fill: "#2563eb" }),
2076
+ /* @__PURE__ */ jsx("circle", { cx: "58", cy: "53", r: "2.5", fill: "#2563eb" }),
2077
+ /* @__PURE__ */ jsx("circle", { cx: "68", cy: "68", r: "2.5", fill: "#2563eb" }),
2078
+ /* @__PURE__ */ jsx("circle", { cx: "78", cy: "68", r: "2.5", fill: "#2563eb" })
2079
+ ]
2080
+ }
2081
+ );
2082
+ }
1797
2083
 
1798
2084
  // src/core/dateLabels.ts
1799
2085
  var SHORT_MONTHS = [
@@ -1885,6 +2171,7 @@ var MessageList = forwardRef(function MessageList2({
1885
2171
  onPin,
1886
2172
  onUnpin,
1887
2173
  onCopy,
2174
+ onRetry,
1888
2175
  pinDisabled,
1889
2176
  className
1890
2177
  }, ref) {
@@ -1949,12 +2236,17 @@ var MessageList = forwardRef(function MessageList2({
1949
2236
  style: styles6.container,
1950
2237
  onScroll: handleScroll,
1951
2238
  children: [
1952
- isLoading && /* @__PURE__ */ jsx("div", { style: styles6.loadingMore, children: "Loading earlier messages..." }),
2239
+ isLoading && messages.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles6.loadingMore, children: [
2240
+ /* @__PURE__ */ jsx(Spinner, { size: 14 }),
2241
+ /* @__PURE__ */ jsx("span", { children: "Loading earlier messages\u2026" })
2242
+ ] }),
1953
2243
  hasMore && !isLoading && messages.length > 0 && /* @__PURE__ */ jsx("button", { onClick: onLoadMore, style: styles6.loadMoreButton, type: "button", children: "Load earlier messages" }),
1954
2244
  messages.length === 0 && !isLoading && /* @__PURE__ */ jsxs("div", { style: styles6.empty, children: [
2245
+ /* @__PURE__ */ jsx(ChatIllustration, { size: 104 }),
1955
2246
  /* @__PURE__ */ jsx("p", { style: styles6.emptyTitle, children: "No messages yet" }),
1956
- /* @__PURE__ */ jsx("p", { style: styles6.emptySubtitle, children: "Start the conversation about this study" })
2247
+ /* @__PURE__ */ jsx("p", { style: styles6.emptySubtitle, children: "Start the conversation \u2014 everyone on this case will see what you write." })
1957
2248
  ] }),
2249
+ messages.length === 0 && isLoading && /* @__PURE__ */ jsx("div", { style: styles6.firstLoad, children: /* @__PURE__ */ jsx(Spinner, { size: 20 }) }),
1958
2250
  messages.map((message, index) => {
1959
2251
  const currentBucket = dayBucketKey(message.insertedAt);
1960
2252
  const prevBucket = index === 0 ? null : dayBucketKey(messages[index - 1].insertedAt);
@@ -1974,6 +2266,7 @@ var MessageList = forwardRef(function MessageList2({
1974
2266
  onPin,
1975
2267
  onUnpin,
1976
2268
  onCopy,
2269
+ onRetry,
1977
2270
  pinDisabled
1978
2271
  }
1979
2272
  ) })
@@ -2002,11 +2295,20 @@ var styles6 = {
2002
2295
  paddingBottom: "8px"
2003
2296
  },
2004
2297
  loadingMore: {
2005
- textAlign: "center",
2298
+ display: "flex",
2299
+ alignItems: "center",
2300
+ justifyContent: "center",
2301
+ gap: "8px",
2006
2302
  padding: "12px",
2007
2303
  fontSize: "14px",
2008
2304
  color: "#6b7280"
2009
2305
  },
2306
+ firstLoad: {
2307
+ display: "flex",
2308
+ alignItems: "center",
2309
+ justifyContent: "center",
2310
+ padding: "48px 16px"
2311
+ },
2010
2312
  loadMoreButton: {
2011
2313
  display: "block",
2012
2314
  margin: "0 auto 12px",
@@ -2031,11 +2333,14 @@ var styles6 = {
2031
2333
  fontSize: "16px",
2032
2334
  fontWeight: 600,
2033
2335
  color: "#374151",
2034
- margin: "0 0 4px"
2336
+ margin: "14px 0 4px"
2035
2337
  },
2036
2338
  emptySubtitle: {
2037
2339
  fontSize: "14px",
2038
- margin: 0
2340
+ margin: 0,
2341
+ maxWidth: "280px",
2342
+ lineHeight: 1.45,
2343
+ textAlign: "center"
2039
2344
  },
2040
2345
  typingIndicator: {
2041
2346
  padding: "4px 16px 8px",
@@ -2314,7 +2619,18 @@ function MessageInput({
2314
2619
  type: "file",
2315
2620
  onChange: handleFileSelect,
2316
2621
  style: { display: "none" },
2317
- accept: [...SUPPORTED_IMAGE_TYPES, "application/pdf", ".doc", ".docx"].join(",")
2622
+ accept: [
2623
+ ...SUPPORTED_IMAGE_TYPES,
2624
+ "application/pdf",
2625
+ ".doc",
2626
+ ".docx",
2627
+ ".xls",
2628
+ ".xlsx",
2629
+ ".csv",
2630
+ ".txt",
2631
+ ".heic",
2632
+ ".heif"
2633
+ ].join(",")
2318
2634
  }
2319
2635
  ),
2320
2636
  /* @__PURE__ */ jsx(
@@ -3039,6 +3355,64 @@ var styles11 = {
3039
3355
  flexShrink: 0
3040
3356
  }
3041
3357
  };
3358
+ function Toast({ toast, onDismiss, durationMs = 4e3 }) {
3359
+ useEffect(() => {
3360
+ if (!toast) return;
3361
+ const timer = setTimeout(onDismiss, durationMs);
3362
+ return () => clearTimeout(timer);
3363
+ }, [toast, onDismiss, durationMs]);
3364
+ if (!toast) return null;
3365
+ return /* @__PURE__ */ jsxs("div", { style: styles12.container, role: "status", "aria-live": "polite", children: [
3366
+ /* @__PURE__ */ jsx("span", { style: styles12.message, children: toast.message }),
3367
+ toast.actionLabel && toast.onAction && /* @__PURE__ */ jsx(
3368
+ "button",
3369
+ {
3370
+ type: "button",
3371
+ onClick: () => {
3372
+ toast.onAction?.();
3373
+ onDismiss();
3374
+ },
3375
+ style: styles12.action,
3376
+ children: toast.actionLabel
3377
+ }
3378
+ )
3379
+ ] });
3380
+ }
3381
+ var styles12 = {
3382
+ container: {
3383
+ position: "absolute",
3384
+ bottom: "76px",
3385
+ left: "50%",
3386
+ transform: "translateX(-50%)",
3387
+ display: "flex",
3388
+ alignItems: "center",
3389
+ gap: "12px",
3390
+ padding: "10px 14px",
3391
+ backgroundColor: "#1e293b",
3392
+ color: "#ffffff",
3393
+ borderRadius: "8px",
3394
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.18)",
3395
+ fontSize: "14px",
3396
+ maxWidth: "90%",
3397
+ zIndex: 20
3398
+ },
3399
+ message: {
3400
+ whiteSpace: "nowrap",
3401
+ overflow: "hidden",
3402
+ textOverflow: "ellipsis"
3403
+ },
3404
+ action: {
3405
+ background: "transparent",
3406
+ border: "none",
3407
+ color: "#93c5fd",
3408
+ fontWeight: 600,
3409
+ fontSize: "14px",
3410
+ cursor: "pointer",
3411
+ padding: "4px 6px",
3412
+ textTransform: "uppercase",
3413
+ letterSpacing: "0.4px"
3414
+ }
3415
+ };
3042
3416
  function CollabPanel({
3043
3417
  orderId,
3044
3418
  patientData,
@@ -3049,6 +3423,7 @@ function CollabPanel({
3049
3423
  }) {
3050
3424
  const { config } = useCollab();
3051
3425
  const [showSettings, setShowSettings] = useState(false);
3426
+ const [toast, setToast] = useState(null);
3052
3427
  const messageListRef = useRef(null);
3053
3428
  const {
3054
3429
  conversation,
@@ -3062,6 +3437,7 @@ function CollabPanel({
3062
3437
  replyTo,
3063
3438
  setReplyTo,
3064
3439
  sendMessage,
3440
+ retryMessage,
3065
3441
  sendAudioMessage,
3066
3442
  sendFileMessage,
3067
3443
  sendTyping,
@@ -3086,10 +3462,33 @@ function CollabPanel({
3086
3462
  try {
3087
3463
  if (navigator.clipboard) {
3088
3464
  await navigator.clipboard.writeText(text);
3465
+ setToast({ message: "Copied to clipboard" });
3089
3466
  }
3090
3467
  } catch {
3091
3468
  }
3092
3469
  };
3470
+ const handlePin = async (messageId) => {
3471
+ try {
3472
+ await pinMessage(messageId);
3473
+ setToast({
3474
+ message: "Pinned",
3475
+ actionLabel: "Undo",
3476
+ onAction: () => {
3477
+ void unpinMessage(messageId);
3478
+ }
3479
+ });
3480
+ } catch {
3481
+ setToast({ message: "Could not pin message" });
3482
+ }
3483
+ };
3484
+ const handleUnpin = async (messageId) => {
3485
+ try {
3486
+ await unpinMessage(messageId);
3487
+ setToast({ message: "Unpinned" });
3488
+ } catch {
3489
+ setToast({ message: "Could not unpin message" });
3490
+ }
3491
+ };
3093
3492
  const pinDisabled = pinnedMessages.length >= MAX_PINNED_MESSAGES;
3094
3493
  if (error) {
3095
3494
  return /* @__PURE__ */ jsx("div", { className, style: { ...panelStyles.container, ...style }, children: /* @__PURE__ */ jsxs("div", { style: panelStyles.errorState, children: [
@@ -3100,78 +3499,83 @@ function CollabPanel({
3100
3499
  if (isLoading && !conversation) {
3101
3500
  return /* @__PURE__ */ jsx("div", { className, style: { ...panelStyles.container, ...style }, children: /* @__PURE__ */ jsx("div", { style: panelStyles.loadingState, children: "Loading conversation..." }) });
3102
3501
  }
3103
- return /* @__PURE__ */ jsx("div", { className, style: { ...panelStyles.container, ...style }, children: showSettings && conversation ? /* @__PURE__ */ jsx(
3104
- ChannelSettings,
3105
- {
3106
- conversationId: conversation.id,
3107
- channelName: conversation.name,
3108
- channelPicture: conversation.picture,
3109
- participants,
3110
- currentUserId: config.userId,
3111
- isAdmin: channelSettings.isAdmin,
3112
- isUpdating: channelSettings.isUpdating,
3113
- error: channelSettings.error,
3114
- onUpdateChannel: channelSettings.updateChannel,
3115
- onInviteUser: channelSettings.inviteUser,
3116
- onRemoveUser: channelSettings.removeUser,
3117
- onLeaveChannel: channelSettings.leaveChannel,
3118
- onDeleteChannel: channelSettings.deleteChannel,
3119
- onClose: () => setShowSettings(false)
3120
- }
3121
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
3122
- /* @__PURE__ */ jsx(
3123
- PatientHeader,
3502
+ return /* @__PURE__ */ jsxs("div", { className, style: { ...panelStyles.container, ...style }, children: [
3503
+ showSettings && conversation ? /* @__PURE__ */ jsx(
3504
+ ChannelSettings,
3124
3505
  {
3125
- patientData,
3506
+ conversationId: conversation.id,
3507
+ channelName: conversation.name,
3508
+ channelPicture: conversation.picture,
3126
3509
  participants,
3127
- onOpenDicom: handleOpenDicom,
3128
- onOpenSettings: () => setShowSettings(true)
3129
- }
3130
- ),
3131
- pinnedMessages.length > 0 && /* @__PURE__ */ jsx(
3132
- PinnedMessagesBar,
3133
- {
3134
- pinnedMessages,
3135
- onJumpTo: handleJumpToMessage,
3136
- onUnpin: unpinMessage
3137
- }
3138
- ),
3139
- /* @__PURE__ */ jsx(
3140
- MessageList,
3141
- {
3142
- ref: messageListRef,
3143
- messages,
3144
3510
  currentUserId: config.userId,
3145
- participants,
3146
- showSeenBy,
3147
- typingUsers,
3148
- hasMore,
3149
- isLoading,
3150
- onLoadMore: loadMoreMessages,
3151
- onDeepLinkClick: handleDeepLink,
3152
- onMessageVisible: markAsRead,
3153
- onReply: setReplyTo,
3154
- onPin: pinMessage,
3155
- onUnpin: unpinMessage,
3156
- onCopy: handleCopy,
3157
- pinDisabled
3511
+ isAdmin: channelSettings.isAdmin,
3512
+ isUpdating: channelSettings.isUpdating,
3513
+ error: channelSettings.error,
3514
+ onUpdateChannel: channelSettings.updateChannel,
3515
+ onInviteUser: channelSettings.inviteUser,
3516
+ onRemoveUser: channelSettings.removeUser,
3517
+ onLeaveChannel: channelSettings.leaveChannel,
3518
+ onDeleteChannel: channelSettings.deleteChannel,
3519
+ onClose: () => setShowSettings(false)
3158
3520
  }
3159
- ),
3160
- /* @__PURE__ */ jsx(
3161
- MessageInput,
3162
- {
3163
- onSendText: sendMessage,
3164
- onSendAudio: sendAudioMessage,
3165
- onSendFile: sendFileMessage,
3166
- onTyping: sendTyping,
3167
- replyTo,
3168
- onCancelReply: () => setReplyTo(null)
3169
- }
3170
- )
3171
- ] }) });
3521
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
3522
+ /* @__PURE__ */ jsx(
3523
+ PatientHeader,
3524
+ {
3525
+ patientData,
3526
+ participants,
3527
+ onOpenDicom: handleOpenDicom,
3528
+ onOpenSettings: () => setShowSettings(true)
3529
+ }
3530
+ ),
3531
+ pinnedMessages.length > 0 && /* @__PURE__ */ jsx(
3532
+ PinnedMessagesBar,
3533
+ {
3534
+ pinnedMessages,
3535
+ onJumpTo: handleJumpToMessage,
3536
+ onUnpin: handleUnpin
3537
+ }
3538
+ ),
3539
+ /* @__PURE__ */ jsx(
3540
+ MessageList,
3541
+ {
3542
+ ref: messageListRef,
3543
+ messages,
3544
+ currentUserId: config.userId,
3545
+ participants,
3546
+ showSeenBy,
3547
+ typingUsers,
3548
+ hasMore,
3549
+ isLoading,
3550
+ onLoadMore: loadMoreMessages,
3551
+ onDeepLinkClick: handleDeepLink,
3552
+ onMessageVisible: markAsRead,
3553
+ onReply: setReplyTo,
3554
+ onPin: handlePin,
3555
+ onUnpin: handleUnpin,
3556
+ onCopy: handleCopy,
3557
+ onRetry: retryMessage,
3558
+ pinDisabled
3559
+ }
3560
+ ),
3561
+ /* @__PURE__ */ jsx(
3562
+ MessageInput,
3563
+ {
3564
+ onSendText: sendMessage,
3565
+ onSendAudio: sendAudioMessage,
3566
+ onSendFile: sendFileMessage,
3567
+ onTyping: sendTyping,
3568
+ replyTo,
3569
+ onCancelReply: () => setReplyTo(null)
3570
+ }
3571
+ )
3572
+ ] }),
3573
+ /* @__PURE__ */ jsx(Toast, { toast, onDismiss: () => setToast(null) })
3574
+ ] });
3172
3575
  }
3173
3576
  var panelStyles = {
3174
3577
  container: {
3578
+ position: "relative",
3175
3579
  display: "flex",
3176
3580
  flexDirection: "column",
3177
3581
  height: "100%",
@@ -3238,6 +3642,7 @@ function ExpandIcon({ size = 18, color = "currentColor" }) {
3238
3642
  }
3239
3643
  );
3240
3644
  }
3645
+ var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
3241
3646
  function CollabPopup({
3242
3647
  orderId,
3243
3648
  patientData,
@@ -3259,6 +3664,8 @@ function CollabPopup({
3259
3664
  const [isMinimized, setIsMinimized] = useState(false);
3260
3665
  const dragOffset = useRef({ x: 0, y: 0 });
3261
3666
  const containerRef = useRef(null);
3667
+ const previouslyFocused = useRef(null);
3668
+ const titleId = useId();
3262
3669
  const handleMouseDown = useCallback(
3263
3670
  (e) => {
3264
3671
  if (!e.target.closest("[data-drag-handle]")) return;
@@ -3273,13 +3680,44 @@ function CollabPopup({
3273
3680
  );
3274
3681
  useEffect(() => {
3275
3682
  if (!isOpen) return;
3683
+ previouslyFocused.current = document.activeElement;
3684
+ const focusFirst = () => {
3685
+ const root = containerRef.current;
3686
+ if (!root) return;
3687
+ const first = root.querySelector(FOCUSABLE_SELECTOR);
3688
+ (first ?? root).focus();
3689
+ };
3690
+ const raf = requestAnimationFrame(focusFirst);
3276
3691
  const handleKey = (e) => {
3277
3692
  if (e.key === "Escape") {
3278
3693
  onClose();
3694
+ return;
3695
+ }
3696
+ if (e.key !== "Tab") return;
3697
+ const root = containerRef.current;
3698
+ if (!root) return;
3699
+ const focusables = Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR));
3700
+ if (focusables.length === 0) {
3701
+ e.preventDefault();
3702
+ return;
3703
+ }
3704
+ const first = focusables[0];
3705
+ const last = focusables[focusables.length - 1];
3706
+ const active = document.activeElement;
3707
+ if (e.shiftKey && active === first) {
3708
+ e.preventDefault();
3709
+ last.focus();
3710
+ } else if (!e.shiftKey && active === last) {
3711
+ e.preventDefault();
3712
+ first.focus();
3279
3713
  }
3280
3714
  };
3281
3715
  window.addEventListener("keydown", handleKey);
3282
- return () => window.removeEventListener("keydown", handleKey);
3716
+ return () => {
3717
+ cancelAnimationFrame(raf);
3718
+ window.removeEventListener("keydown", handleKey);
3719
+ previouslyFocused.current?.focus?.();
3720
+ };
3283
3721
  }, [isOpen, onClose]);
3284
3722
  useEffect(() => {
3285
3723
  if (!isDragging) return;
@@ -3305,8 +3743,12 @@ function CollabPopup({
3305
3743
  {
3306
3744
  ref: containerRef,
3307
3745
  className,
3746
+ role: "dialog",
3747
+ "aria-modal": "true",
3748
+ "aria-labelledby": titleId,
3749
+ tabIndex: -1,
3308
3750
  style: {
3309
- ...styles12.container,
3751
+ ...styles13.container,
3310
3752
  left: position.x,
3311
3753
  top: position.y,
3312
3754
  width,
@@ -3315,14 +3757,14 @@ function CollabPopup({
3315
3757
  },
3316
3758
  onMouseDown: handleMouseDown,
3317
3759
  children: [
3318
- /* @__PURE__ */ jsxs("div", { "data-drag-handle": true, style: styles12.titleBar, children: [
3319
- /* @__PURE__ */ jsx("span", { style: styles12.titleText, children: buildChannelName(patientData) }),
3320
- /* @__PURE__ */ jsxs("div", { style: styles12.titleActions, children: [
3760
+ /* @__PURE__ */ jsxs("div", { "data-drag-handle": true, style: styles13.titleBar, children: [
3761
+ /* @__PURE__ */ jsx("span", { id: titleId, style: styles13.titleText, children: buildChannelName(patientData) }),
3762
+ /* @__PURE__ */ jsxs("div", { style: styles13.titleActions, children: [
3321
3763
  /* @__PURE__ */ jsx(
3322
3764
  "button",
3323
3765
  {
3324
3766
  onClick: () => setIsMinimized(!isMinimized),
3325
- style: styles12.titleButton,
3767
+ style: styles13.titleButton,
3326
3768
  "aria-label": isMinimized ? "Expand chat" : "Minimize chat",
3327
3769
  title: isMinimized ? "Expand" : "Minimize",
3328
3770
  type: "button",
@@ -3333,7 +3775,7 @@ function CollabPopup({
3333
3775
  "button",
3334
3776
  {
3335
3777
  onClick: onClose,
3336
- style: styles12.titleButton,
3778
+ style: styles13.titleButton,
3337
3779
  "aria-label": "Close chat",
3338
3780
  title: "Close",
3339
3781
  type: "button",
@@ -3342,12 +3784,12 @@ function CollabPopup({
3342
3784
  )
3343
3785
  ] })
3344
3786
  ] }),
3345
- !isMinimized && /* @__PURE__ */ jsx("div", { style: styles12.panelWrapper, children: /* @__PURE__ */ jsx(CollabPanel, { orderId, patientData, participantIds }) })
3787
+ !isMinimized && /* @__PURE__ */ jsx("div", { style: styles13.panelWrapper, children: /* @__PURE__ */ jsx(CollabPanel, { orderId, patientData, participantIds }) })
3346
3788
  ]
3347
3789
  }
3348
3790
  );
3349
3791
  }
3350
- var styles12 = {
3792
+ var styles13 = {
3351
3793
  container: {
3352
3794
  position: "fixed",
3353
3795
  zIndex: 9999,
@@ -3652,11 +4094,11 @@ function CollabInline({
3652
4094
  containerRef
3653
4095
  } = useInlineCollab({ orderId, patientData, participantIds, messageLimit });
3654
4096
  if (isLoading && !hasConversation) {
3655
- return /* @__PURE__ */ jsx("div", { className, style: { ...styles13.container, ...style }, children: /* @__PURE__ */ jsx("div", { style: styles13.loadingState, children: "Loading..." }) });
4097
+ return /* @__PURE__ */ jsx("div", { className, style: { ...styles14.container, ...style }, children: /* @__PURE__ */ jsx("div", { style: styles14.loadingState, children: "Loading..." }) });
3656
4098
  }
3657
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className, style: { ...styles13.container, ...style }, children: [
3658
- hasConversation && messages.length > 0 && /* @__PURE__ */ jsx("div", { style: styles13.preview, children: messages.slice(-messageLimit).map((message) => /* @__PURE__ */ jsx(InlineMessageRow, { message }, message.id)) }),
3659
- error && /* @__PURE__ */ jsx("div", { style: styles13.error, children: error }),
4099
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className, style: { ...styles14.container, ...style }, children: [
4100
+ hasConversation && messages.length > 0 && /* @__PURE__ */ jsx("div", { style: styles14.preview, children: messages.slice(-messageLimit).map((message) => /* @__PURE__ */ jsx(InlineMessageRow, { message }, message.id)) }),
4101
+ error && /* @__PURE__ */ jsx("div", { style: styles14.error, children: error }),
3660
4102
  /* @__PURE__ */ jsx(
3661
4103
  InlineInputBar,
3662
4104
  {
@@ -3673,12 +4115,12 @@ function CollabInline({
3673
4115
  }
3674
4116
  function InlineMessageRow({ message }) {
3675
4117
  if (message.type === "system") {
3676
- return /* @__PURE__ */ jsx("div", { style: styles13.systemRow, children: /* @__PURE__ */ jsx("span", { style: styles13.systemText, children: message.body }) });
4118
+ return /* @__PURE__ */ jsx("div", { style: styles14.systemRow, children: /* @__PURE__ */ jsx("span", { style: styles14.systemText, children: message.body }) });
3677
4119
  }
3678
4120
  if (message.type === "audio" && message.mediaUrl) {
3679
- return /* @__PURE__ */ jsxs("div", { style: styles13.messageRow, children: [
4121
+ return /* @__PURE__ */ jsxs("div", { style: styles14.messageRow, children: [
3680
4122
  /* @__PURE__ */ jsx(RoleDot, { role: message.senderRole }),
3681
- /* @__PURE__ */ jsxs("span", { style: styles13.senderName, children: [
4123
+ /* @__PURE__ */ jsxs("span", { style: styles14.senderName, children: [
3682
4124
  message.senderName,
3683
4125
  ":"
3684
4126
  ] }),
@@ -3686,13 +4128,13 @@ function InlineMessageRow({ message }) {
3686
4128
  ] });
3687
4129
  }
3688
4130
  const preview = renderMessagePreview(message);
3689
- return /* @__PURE__ */ jsxs("div", { style: styles13.messageRow, children: [
4131
+ return /* @__PURE__ */ jsxs("div", { style: styles14.messageRow, children: [
3690
4132
  /* @__PURE__ */ jsx(RoleDot, { role: message.senderRole }),
3691
- /* @__PURE__ */ jsxs("span", { style: styles13.senderName, children: [
4133
+ /* @__PURE__ */ jsxs("span", { style: styles14.senderName, children: [
3692
4134
  message.senderName,
3693
4135
  ":"
3694
4136
  ] }),
3695
- /* @__PURE__ */ jsx("span", { style: styles13.messageText, children: preview })
4137
+ /* @__PURE__ */ jsx("span", { style: styles14.messageText, children: preview })
3696
4138
  ] });
3697
4139
  }
3698
4140
  function RoleDot({ role }) {
@@ -3702,7 +4144,7 @@ function RoleDot({ role }) {
3702
4144
  physician: "#059669",
3703
4145
  admin: "#dc2626"
3704
4146
  };
3705
- return /* @__PURE__ */ jsx("span", { style: { ...styles13.roleDot, backgroundColor: colors[role] } });
4147
+ return /* @__PURE__ */ jsx("span", { style: { ...styles14.roleDot, backgroundColor: colors[role] } });
3706
4148
  }
3707
4149
  function InlineAudioPlayer({ url, duration }) {
3708
4150
  const audioRef = useRef(null);
@@ -3746,27 +4188,27 @@ function InlineAudioPlayer({ url, duration }) {
3746
4188
  const total = duration ?? 0;
3747
4189
  const remaining = Math.max(0, total - Math.floor(currentTime));
3748
4190
  const displayTime = isPlaying ? remaining : total;
3749
- return /* @__PURE__ */ jsxs("span", { style: styles13.audioPlayer, children: [
4191
+ return /* @__PURE__ */ jsxs("span", { style: styles14.audioPlayer, children: [
3750
4192
  /* @__PURE__ */ jsx(
3751
4193
  "button",
3752
4194
  {
3753
4195
  onClick: toggle,
3754
- style: styles13.audioButton,
4196
+ style: styles14.audioButton,
3755
4197
  title: isPlaying ? "Pause" : "Play",
3756
4198
  type: "button",
3757
4199
  children: isPlaying ? "\u23F8" : "\u25B6"
3758
4200
  }
3759
4201
  ),
3760
- /* @__PURE__ */ jsxs("span", { style: styles13.audioWaveform, children: [
3761
- /* @__PURE__ */ jsx("span", { style: { ...styles13.waveBar, height: "40%" } }),
3762
- /* @__PURE__ */ jsx("span", { style: { ...styles13.waveBar, height: "80%" } }),
3763
- /* @__PURE__ */ jsx("span", { style: { ...styles13.waveBar, height: "60%" } }),
3764
- /* @__PURE__ */ jsx("span", { style: { ...styles13.waveBar, height: "90%" } }),
3765
- /* @__PURE__ */ jsx("span", { style: { ...styles13.waveBar, height: "50%" } }),
3766
- /* @__PURE__ */ jsx("span", { style: { ...styles13.waveBar, height: "70%" } }),
3767
- /* @__PURE__ */ jsx("span", { style: { ...styles13.waveBar, height: "40%" } })
4202
+ /* @__PURE__ */ jsxs("span", { style: styles14.audioWaveform, children: [
4203
+ /* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "40%" } }),
4204
+ /* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "80%" } }),
4205
+ /* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "60%" } }),
4206
+ /* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "90%" } }),
4207
+ /* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "50%" } }),
4208
+ /* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "70%" } }),
4209
+ /* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "40%" } })
3768
4210
  ] }),
3769
- /* @__PURE__ */ jsx("span", { style: styles13.audioDuration, children: formatDuration2(displayTime) }),
4211
+ /* @__PURE__ */ jsx("span", { style: styles14.audioDuration, children: formatDuration2(displayTime) }),
3770
4212
  /* @__PURE__ */ jsx("audio", { ref: audioRef, src: url, preload: "metadata" })
3771
4213
  ] });
3772
4214
  }
@@ -3822,7 +4264,7 @@ function InlineInputBar({
3822
4264
  },
3823
4265
  [handleSend]
3824
4266
  );
3825
- return /* @__PURE__ */ jsxs("div", { style: styles13.inputBar, children: [
4267
+ return /* @__PURE__ */ jsxs("div", { style: styles14.inputBar, children: [
3826
4268
  /* @__PURE__ */ jsx(
3827
4269
  "input",
3828
4270
  {
@@ -3833,17 +4275,17 @@ function InlineInputBar({
3833
4275
  onKeyDown: handleKeyDown,
3834
4276
  placeholder,
3835
4277
  disabled: isSending,
3836
- style: styles13.input
4278
+ style: styles14.input
3837
4279
  }
3838
4280
  ),
3839
- unreadCount > 0 && /* @__PURE__ */ jsx("span", { style: styles13.unreadBadge, title: `${unreadCount} unread`, children: unreadCount > 99 ? "99+" : unreadCount }),
3840
- isLive && /* @__PURE__ */ jsx("span", { style: styles13.liveDot, title: "Live updates active" }),
4281
+ unreadCount > 0 && /* @__PURE__ */ jsx("span", { style: styles14.unreadBadge, title: `${unreadCount} unread`, children: unreadCount > 99 ? "99+" : unreadCount }),
4282
+ isLive && /* @__PURE__ */ jsx("span", { style: styles14.liveDot, title: "Live updates active" }),
3841
4283
  text.trim() && /* @__PURE__ */ jsx(
3842
4284
  "button",
3843
4285
  {
3844
4286
  onClick: handleSend,
3845
4287
  disabled: isSending,
3846
- style: styles13.sendButton,
4288
+ style: styles14.sendButton,
3847
4289
  title: "Send message",
3848
4290
  type: "button",
3849
4291
  children: "\u27A4"
@@ -3853,7 +4295,7 @@ function InlineInputBar({
3853
4295
  "button",
3854
4296
  {
3855
4297
  onClick: onExpand,
3856
- style: styles13.expandButton,
4298
+ style: styles14.expandButton,
3857
4299
  title: "Expand to full chat",
3858
4300
  type: "button",
3859
4301
  children: "\u26F6"
@@ -3861,7 +4303,7 @@ function InlineInputBar({
3861
4303
  )
3862
4304
  ] });
3863
4305
  }
3864
- var styles13 = {
4306
+ var styles14 = {
3865
4307
  container: {
3866
4308
  display: "flex",
3867
4309
  flexDirection: "column",
@@ -4128,24 +4570,24 @@ function ConversationListItem({
4128
4570
  className,
4129
4571
  onClick,
4130
4572
  style: {
4131
- ...styles14.container,
4132
- ...isSelected ? styles14.selected : {},
4133
- ...hasUnread ? styles14.unread : {}
4573
+ ...styles15.container,
4574
+ ...isSelected ? styles15.selected : {},
4575
+ ...hasUnread ? styles15.unread : {}
4134
4576
  },
4135
4577
  type: "button",
4136
4578
  children: [
4137
- /* @__PURE__ */ jsxs("div", { style: styles14.avatarWrapper, children: [
4138
- item.picture ? /* @__PURE__ */ jsx("img", { src: item.picture, alt: "", style: styles14.avatar }) : /* @__PURE__ */ jsx("div", { style: styles14.avatarFallback, children: (displayName).charAt(0).toUpperCase() }),
4139
- hasUnread && /* @__PURE__ */ jsx("span", { style: styles14.unreadDot })
4579
+ /* @__PURE__ */ jsxs("div", { style: styles15.avatarWrapper, children: [
4580
+ item.picture ? /* @__PURE__ */ jsx("img", { src: item.picture, alt: "", style: styles15.avatar }) : /* @__PURE__ */ jsx("div", { style: styles15.avatarFallback, children: (displayName).charAt(0).toUpperCase() }),
4581
+ hasUnread && /* @__PURE__ */ jsx("span", { style: styles15.unreadDot })
4140
4582
  ] }),
4141
- /* @__PURE__ */ jsxs("div", { style: styles14.content, children: [
4142
- /* @__PURE__ */ jsxs("div", { style: styles14.topRow, children: [
4583
+ /* @__PURE__ */ jsxs("div", { style: styles15.content, children: [
4584
+ /* @__PURE__ */ jsxs("div", { style: styles15.topRow, children: [
4143
4585
  /* @__PURE__ */ jsx(
4144
4586
  "span",
4145
4587
  {
4146
4588
  style: {
4147
- ...styles14.name,
4148
- ...hasUnread ? styles14.nameUnread : {}
4589
+ ...styles15.name,
4590
+ ...hasUnread ? styles15.nameUnread : {}
4149
4591
  },
4150
4592
  children: displayName
4151
4593
  }
@@ -4154,25 +4596,25 @@ function ConversationListItem({
4154
4596
  "span",
4155
4597
  {
4156
4598
  style: {
4157
- ...styles14.time,
4158
- ...hasUnread ? styles14.timeUnread : {}
4599
+ ...styles15.time,
4600
+ ...hasUnread ? styles15.timeUnread : {}
4159
4601
  },
4160
4602
  children: formatInboxTimestamp(item.lastActivityAt)
4161
4603
  }
4162
4604
  )
4163
4605
  ] }),
4164
- /* @__PURE__ */ jsxs("div", { style: styles14.bottomRow, children: [
4606
+ /* @__PURE__ */ jsxs("div", { style: styles15.bottomRow, children: [
4165
4607
  /* @__PURE__ */ jsx(
4166
4608
  "span",
4167
4609
  {
4168
4610
  style: {
4169
- ...styles14.preview,
4170
- ...hasUnread ? styles14.previewUnread : {}
4611
+ ...styles15.preview,
4612
+ ...hasUnread ? styles15.previewUnread : {}
4171
4613
  },
4172
4614
  children: renderLastMessagePreview(item.lastMessage)
4173
4615
  }
4174
4616
  ),
4175
- hasUnread && /* @__PURE__ */ jsx("span", { style: styles14.badge, children: item.unreadCount > 99 ? "99+" : item.unreadCount })
4617
+ hasUnread && /* @__PURE__ */ jsx("span", { style: styles15.badge, children: item.unreadCount > 99 ? "99+" : item.unreadCount })
4176
4618
  ] })
4177
4619
  ] })
4178
4620
  ]
@@ -4200,7 +4642,7 @@ function renderLastMessagePreview(message) {
4200
4642
  }
4201
4643
  }
4202
4644
  }
4203
- var styles14 = {
4645
+ var styles15 = {
4204
4646
  container: {
4205
4647
  display: "flex",
4206
4648
  alignItems: "center",
@@ -4347,30 +4789,30 @@ function ConversationList({
4347
4789
  return result;
4348
4790
  }, [conversations, query, showUnreadOnly]);
4349
4791
  const totalUnread = conversations.reduce((sum, c) => sum + c.unreadCount, 0);
4350
- return /* @__PURE__ */ jsxs("div", { className, style: styles15.container, children: [
4351
- /* @__PURE__ */ jsxs("div", { style: styles15.header, children: [
4352
- /* @__PURE__ */ jsxs("div", { style: styles15.titleRow, children: [
4353
- /* @__PURE__ */ jsx("h3", { style: styles15.title, children: title }),
4354
- totalUnread > 0 && /* @__PURE__ */ jsx("span", { style: styles15.totalBadge, children: totalUnread })
4792
+ return /* @__PURE__ */ jsxs("div", { className, style: styles16.container, children: [
4793
+ /* @__PURE__ */ jsxs("div", { style: styles16.header, children: [
4794
+ /* @__PURE__ */ jsxs("div", { style: styles16.titleRow, children: [
4795
+ /* @__PURE__ */ jsx("h3", { style: styles16.title, children: title }),
4796
+ totalUnread > 0 && /* @__PURE__ */ jsx("span", { style: styles16.totalBadge, children: totalUnread })
4355
4797
  ] }),
4356
- /* @__PURE__ */ jsx("div", { style: styles15.searchRow, children: /* @__PURE__ */ jsx(
4798
+ /* @__PURE__ */ jsx("div", { style: styles16.searchRow, children: /* @__PURE__ */ jsx(
4357
4799
  "input",
4358
4800
  {
4359
4801
  type: "text",
4360
4802
  value: query,
4361
4803
  onChange: (e) => setQuery(e.target.value),
4362
4804
  placeholder: "Search patients or channels...",
4363
- style: styles15.searchInput
4805
+ style: styles16.searchInput
4364
4806
  }
4365
4807
  ) }),
4366
- /* @__PURE__ */ jsxs("div", { style: styles15.filterRow, children: [
4808
+ /* @__PURE__ */ jsxs("div", { style: styles16.filterRow, children: [
4367
4809
  /* @__PURE__ */ jsx(
4368
4810
  "button",
4369
4811
  {
4370
4812
  onClick: () => setShowUnreadOnly(false),
4371
4813
  style: {
4372
- ...styles15.filterButton,
4373
- ...!showUnreadOnly ? styles15.filterButtonActive : {}
4814
+ ...styles16.filterButton,
4815
+ ...!showUnreadOnly ? styles16.filterButtonActive : {}
4374
4816
  },
4375
4817
  type: "button",
4376
4818
  children: "All"
@@ -4381,8 +4823,8 @@ function ConversationList({
4381
4823
  {
4382
4824
  onClick: () => setShowUnreadOnly(true),
4383
4825
  style: {
4384
- ...styles15.filterButton,
4385
- ...showUnreadOnly ? styles15.filterButtonActive : {}
4826
+ ...styles16.filterButton,
4827
+ ...showUnreadOnly ? styles16.filterButtonActive : {}
4386
4828
  },
4387
4829
  type: "button",
4388
4830
  children: [
@@ -4393,10 +4835,10 @@ function ConversationList({
4393
4835
  )
4394
4836
  ] })
4395
4837
  ] }),
4396
- /* @__PURE__ */ jsxs("div", { style: styles15.list, children: [
4397
- isLoading && /* @__PURE__ */ jsx("div", { style: styles15.state, children: "Loading conversations..." }),
4398
- error && /* @__PURE__ */ jsx("div", { style: styles15.errorState, children: error }),
4399
- !isLoading && !error && filtered.length === 0 && /* @__PURE__ */ jsx("div", { style: styles15.state, children: query ? "No matching conversations" : "No conversations yet" }),
4838
+ /* @__PURE__ */ jsxs("div", { style: styles16.list, children: [
4839
+ isLoading && /* @__PURE__ */ jsx("div", { style: styles16.state, children: "Loading conversations..." }),
4840
+ error && /* @__PURE__ */ jsx("div", { style: styles16.errorState, children: error }),
4841
+ !isLoading && !error && filtered.length === 0 && /* @__PURE__ */ jsx("div", { style: styles16.state, children: query ? "No matching conversations" : "No conversations yet" }),
4400
4842
  filtered.map((item) => /* @__PURE__ */ jsx(
4401
4843
  ConversationListItem,
4402
4844
  {
@@ -4409,7 +4851,7 @@ function ConversationList({
4409
4851
  ] })
4410
4852
  ] });
4411
4853
  }
4412
- var styles15 = {
4854
+ var styles16 = {
4413
4855
  container: {
4414
4856
  display: "flex",
4415
4857
  flexDirection: "column",
@@ -4514,8 +4956,8 @@ function CollabInbox({
4514
4956
  setSelectedId(item.id);
4515
4957
  onSelectConversation?.(item);
4516
4958
  };
4517
- return /* @__PURE__ */ jsxs("div", { className, style: { ...styles16.container, ...style }, children: [
4518
- /* @__PURE__ */ jsx("div", { style: styles16.sidebar, children: /* @__PURE__ */ jsx(
4959
+ return /* @__PURE__ */ jsxs("div", { className, style: { ...styles17.container, ...style }, children: [
4960
+ /* @__PURE__ */ jsx("div", { style: styles17.sidebar, children: /* @__PURE__ */ jsx(
4519
4961
  ConversationList,
4520
4962
  {
4521
4963
  conversations,
@@ -4526,13 +4968,13 @@ function CollabInbox({
4526
4968
  title
4527
4969
  }
4528
4970
  ) }),
4529
- /* @__PURE__ */ jsx("div", { style: styles16.main, children: selected ? /* @__PURE__ */ jsx(
4971
+ /* @__PURE__ */ jsx("div", { style: styles17.main, children: selected ? /* @__PURE__ */ jsx(
4530
4972
  CollabPanel,
4531
4973
  {
4532
4974
  orderId: selected.orderId,
4533
4975
  patientData: buildPatientData(selected),
4534
4976
  showSeenBy: true,
4535
- style: styles16.panel
4977
+ style: styles17.panel
4536
4978
  },
4537
4979
  selected.id
4538
4980
  ) : /* @__PURE__ */ jsx(EmptyState, { hasAny: conversations.length > 0 }) })
@@ -4546,13 +4988,13 @@ function buildPatientData(item) {
4546
4988
  };
4547
4989
  }
4548
4990
  function EmptyState({ hasAny }) {
4549
- return /* @__PURE__ */ jsxs("div", { style: styles16.empty, children: [
4550
- /* @__PURE__ */ jsx("div", { style: styles16.emptyIcon, children: "\u{1F4AC}" }),
4551
- /* @__PURE__ */ jsx("h3", { style: styles16.emptyTitle, children: hasAny ? "Select a conversation" : "No conversations yet" }),
4552
- /* @__PURE__ */ jsx("p", { style: styles16.emptySubtitle, children: hasAny ? "Pick a patient from the list to start collaborating" : "Conversations appear here once you start messaging about a case" })
4991
+ return /* @__PURE__ */ jsxs("div", { style: styles17.empty, children: [
4992
+ /* @__PURE__ */ jsx("div", { style: styles17.emptyIcon, children: "\u{1F4AC}" }),
4993
+ /* @__PURE__ */ jsx("h3", { style: styles17.emptyTitle, children: hasAny ? "Select a conversation" : "No conversations yet" }),
4994
+ /* @__PURE__ */ jsx("p", { style: styles17.emptySubtitle, children: hasAny ? "Pick a patient from the list to start collaborating" : "Conversations appear here once you start messaging about a case" })
4553
4995
  ] });
4554
4996
  }
4555
- var styles16 = {
4997
+ var styles17 = {
4556
4998
  container: {
4557
4999
  display: "flex",
4558
5000
  height: "100%",