@natoe/colab 0.1.11 → 0.1.13

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
@@ -321,6 +321,173 @@ var CollabSocket = class {
321
321
  this.config = null;
322
322
  }
323
323
  };
324
+
325
+ // src/core/theme.ts
326
+ var FONT_SIZE = {
327
+ xs: "12px",
328
+ sm: "13px",
329
+ md: "15px",
330
+ lg: "17px",
331
+ xxl: "28px"
332
+ };
333
+ var FONT_WEIGHT = {
334
+ regular: 400,
335
+ medium: 500,
336
+ semibold: 600,
337
+ bold: 700
338
+ };
339
+ var LINE_HEIGHT = {
340
+ tight: 1.3,
341
+ normal: 1.45};
342
+ var SPACE = {
343
+ S1: "4px",
344
+ S2: "8px",
345
+ S3: "12px",
346
+ S4: "16px",
347
+ S5: "20px",
348
+ S6: "24px",
349
+ S12: "48px"
350
+ };
351
+ var RADIUS = {
352
+ sm: "4px",
353
+ md: "8px",
354
+ lg: "12px",
355
+ xl: "16px",
356
+ pill: "9999px",
357
+ full: "50%"
358
+ };
359
+ var THEME_VAR = {
360
+ primary: "--natoe-colab-primary",
361
+ primaryHover: "--natoe-colab-primary-hover",
362
+ primaryBg: "--natoe-colab-primary-bg",
363
+ primaryFg: "--natoe-colab-primary-fg",
364
+ success: "--natoe-colab-success",
365
+ warning: "--natoe-colab-warning",
366
+ danger: "--natoe-colab-danger",
367
+ dangerBg: "--natoe-colab-danger-bg",
368
+ dangerBorder: "--natoe-colab-danger-border",
369
+ dangerFg: "--natoe-colab-danger-fg",
370
+ fontStack: "--natoe-colab-font-stack"
371
+ };
372
+ var THEME_DEFAULTS = {
373
+ primary: "#2563eb",
374
+ primaryHover: "#1d4ed8",
375
+ primaryBg: "#dbeafe",
376
+ primaryFg: "#1d4ed8",
377
+ success: "#059669",
378
+ warning: "#d97706",
379
+ danger: "#dc2626",
380
+ dangerBg: "#fef2f2",
381
+ dangerBorder: "#fecaca",
382
+ dangerFg: "#b91c1c",
383
+ fontStack: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
384
+ };
385
+ var cssVar = (name, fallback) => `var(${name}, ${fallback})`;
386
+ var COLOR = {
387
+ // Brand — themeable
388
+ primary: cssVar(THEME_VAR.primary, THEME_DEFAULTS.primary),
389
+ primaryHover: cssVar(THEME_VAR.primaryHover, THEME_DEFAULTS.primaryHover),
390
+ /** Tinted surface for chips/cards on brand-coloured states. */
391
+ primaryBg: cssVar(THEME_VAR.primaryBg, THEME_DEFAULTS.primaryBg),
392
+ primaryFg: cssVar(THEME_VAR.primaryFg, THEME_DEFAULTS.primaryFg),
393
+ // Neutral grayscale — not themeable, taken from Tailwind's zinc-leaning
394
+ // slate to match the package's existing tonal balance.
395
+ white: "#ffffff",
396
+ neutral50: "#f9fafb",
397
+ neutral100: "#f3f4f6",
398
+ neutral200: "#e5e7eb",
399
+ neutral300: "#d1d5db",
400
+ neutral400: "#9ca3af",
401
+ neutral500: "#6b7280",
402
+ neutral600: "#4b5563",
403
+ neutral700: "#374151",
404
+ neutral800: "#1f2937",
405
+ neutral900: "#111827",
406
+ slate800: "#1e293b",
407
+ // Semantic — themeable
408
+ success: cssVar(THEME_VAR.success, THEME_DEFAULTS.success),
409
+ warning: cssVar(THEME_VAR.warning, THEME_DEFAULTS.warning),
410
+ danger: cssVar(THEME_VAR.danger, THEME_DEFAULTS.danger),
411
+ dangerBg: cssVar(THEME_VAR.dangerBg, THEME_DEFAULTS.dangerBg),
412
+ dangerBorder: cssVar(THEME_VAR.dangerBorder, THEME_DEFAULTS.dangerBorder),
413
+ dangerFg: cssVar(THEME_VAR.dangerFg, THEME_DEFAULTS.dangerFg)
414
+ };
415
+ var SIZE = {
416
+ /** Default interactive height (text buttons, list rows). */
417
+ control: "40px",
418
+ /** Square icon-only buttons inside chrome (popup title, menu trigger). */
419
+ controlIcon: "36px"};
420
+ var SHADOW = {
421
+ /** Cards / floating popovers (message-actions menu). */
422
+ popover: "0 6px 18px rgba(0, 0, 0, 0.12)",
423
+ toast: "0 4px 12px rgba(0, 0, 0, 0.18)"
424
+ };
425
+ var Z_INDEX = {
426
+ /** Sticky day divider — above bubbles, below interactive popovers. */
427
+ sticky: 1,
428
+ /** Toasts inside a panel. */
429
+ toast: 20,
430
+ /** Floating CollabPopup dialog. */
431
+ dialog: 9999,
432
+ /** Portaled message-actions menu — must sit above the dialog. */
433
+ menu: 1e4
434
+ };
435
+
436
+ // src/core/styles.ts
437
+ var FONT_STACK = `var(${THEME_VAR.fontStack}, ${THEME_DEFAULTS.fontStack})`;
438
+ var ROOT_CLASS = "natoe-colab-root";
439
+ var GLOBAL_STYLE_ID = "natoe-colab-global-styles";
440
+ var ROOT_DEFAULTS_BLOCK = Object.keys(THEME_DEFAULTS).map((key) => ` ${THEME_VAR[key]}: ${THEME_DEFAULTS[key]};`).join("\n");
441
+ var GLOBAL_CSS = `
442
+ :root {
443
+ ${ROOT_DEFAULTS_BLOCK}
444
+ }
445
+
446
+ @keyframes natoe-colab-spin { to { transform: rotate(360deg); } }
447
+
448
+ @keyframes natoe-colab-message-in {
449
+ from { opacity: 0; transform: translateY(4px); }
450
+ to { opacity: 1; transform: translateY(0); }
451
+ }
452
+
453
+ /* High-contrast adjustments \u2014 older users on OS-level high-contrast mode
454
+ get heavier borders and stronger text without losing the package look. */
455
+ @media (prefers-contrast: more) {
456
+ .${ROOT_CLASS} {
457
+ color: #000000;
458
+ }
459
+ .${ROOT_CLASS} button {
460
+ outline: 1px solid currentColor;
461
+ }
462
+ }
463
+
464
+ /* Honour reduced-motion preferences by killing entry animations. */
465
+ @media (prefers-reduced-motion: reduce) {
466
+ .${ROOT_CLASS} [data-natoe-message-bubble] {
467
+ animation: none !important;
468
+ }
469
+ }
470
+ `;
471
+ function ensureGlobalStyles() {
472
+ if (typeof document === "undefined") return;
473
+ if (document.getElementById(GLOBAL_STYLE_ID)) return;
474
+ const style = document.createElement("style");
475
+ style.id = GLOBAL_STYLE_ID;
476
+ style.textContent = GLOBAL_CSS;
477
+ document.head.appendChild(style);
478
+ }
479
+ function applyThemeOverrides(theme) {
480
+ if (typeof document === "undefined") return;
481
+ const root = document.documentElement;
482
+ Object.keys(THEME_VAR).forEach((key) => {
483
+ const value = theme?.[key];
484
+ if (value) {
485
+ root.style.setProperty(THEME_VAR[key], value);
486
+ } else {
487
+ root.style.removeProperty(THEME_VAR[key]);
488
+ }
489
+ });
490
+ }
324
491
  var CollabContext = React4.createContext(null);
325
492
  function useCollab() {
326
493
  const context = React4.useContext(CollabContext);
@@ -339,6 +506,9 @@ function CollabProvider({ config, apiBaseUrl, children }) {
339
506
  const pendingResolvers = React4.useRef(/* @__PURE__ */ new Map());
340
507
  const previewCache = React4.useRef(/* @__PURE__ */ new Map());
341
508
  const batchScheduled = React4.useRef(false);
509
+ React4.useEffect(() => {
510
+ applyThemeOverrides(config.theme);
511
+ }, [config.theme]);
342
512
  React4.useEffect(() => {
343
513
  if (typeof window === "undefined") return;
344
514
  let s = socket;
@@ -554,6 +724,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
554
724
  config,
555
725
  apiBaseUrl,
556
726
  totalUnread,
727
+ unreadCounts,
557
728
  requestPreview,
558
729
  invalidatePreview,
559
730
  fetchMessages,
@@ -575,7 +746,11 @@ function buildChannelName(patientData) {
575
746
  const patientPart = patientData.patientName?.trim() || "Unknown";
576
747
  const labPart = patientData.labName?.trim()?.slice(0, 24) || "";
577
748
  const last4 = (patientData.displayOrderId ?? "").trim().slice(-4) || "0000";
578
- return `${patientPart} - ${labPart} - ${last4}`;
749
+ return labPart ? `${patientPart} - ${labPart} - ${last4}` : `${patientPart} - ${last4}`;
750
+ }
751
+ function cleanChannelName(name) {
752
+ if (!name) return "";
753
+ return name.replace(/\s+-\s+-\s+/g, " - ").trim();
579
754
  }
580
755
 
581
756
  // src/hooks/useConversation.ts
@@ -610,8 +785,11 @@ function useConversation({
610
785
  const ensureConversationInFlight = React4.useRef(null);
611
786
  const markedReadIdsRef = React4.useRef(/* @__PURE__ */ new Set());
612
787
  const buildName = React4.useCallback(
613
- () => buildChannelName(patientData),
614
- [patientData]
788
+ () => {
789
+ const labName = patientData.labName ?? (config.userRole === "lab" ? config.userName : void 0);
790
+ return buildChannelName({ ...patientData, labName });
791
+ },
792
+ [patientData, config.userRole, config.userName]
615
793
  );
616
794
  const joinChannel = React4.useCallback(
617
795
  (conv) => {
@@ -724,26 +902,22 @@ function useConversation({
724
902
  setConversation(existingConv);
725
903
  setParticipants(preview.participants);
726
904
  joinChannel(existingConv);
727
- setMessages(preview.lastMessages);
728
- setHasMore(preview.messageCount > preview.lastMessages.length);
729
- setPinnedMessages(preview.lastMessages.filter((m) => m.isPinned));
730
- setIsLoading(false);
731
905
  if (loadHistory) {
732
906
  fetchMessages(preview.conversationId).then((history) => {
733
907
  if (cancelled) return;
734
- setMessages((prev) => {
735
- const seen = new Set(prev.map((m) => m.id));
736
- const older = history.filter((m) => !seen.has(m.id));
737
- return [...older, ...prev];
738
- });
739
- setHasMore(history.length > 0);
740
- setPinnedMessages((prev) => {
741
- const fromHistory = history.filter((m) => m.isPinned);
742
- const seen = new Set(prev.map((m) => m.id));
743
- return [...fromHistory.filter((m) => !seen.has(m.id)), ...prev];
744
- });
908
+ setMessages(history);
909
+ setHasMore(history.length >= MESSAGES_PAGE_SIZE);
910
+ setPinnedMessages(history.filter((m) => m.isPinned));
911
+ setIsLoading(false);
745
912
  }).catch(() => {
913
+ if (cancelled) return;
914
+ setIsLoading(false);
746
915
  });
916
+ } else {
917
+ setMessages([]);
918
+ setHasMore(false);
919
+ setPinnedMessages([]);
920
+ setIsLoading(false);
747
921
  }
748
922
  } catch (err) {
749
923
  if (cancelled) return;
@@ -824,7 +998,7 @@ function useConversation({
824
998
  const payload = {
825
999
  body,
826
1000
  type: "text",
827
- ...replyTo ? { replyToId: replyTo.id } : {}
1001
+ ...replyTo ? { reply_to_id: replyTo.id } : {}
828
1002
  };
829
1003
  const { conv, persistedByCreate } = await ensureConversation(payload);
830
1004
  setReplyTo(null);
@@ -877,7 +1051,7 @@ function useConversation({
877
1051
  const payload = {
878
1052
  body: target.body,
879
1053
  type: "text",
880
- ...target.replyToId ? { replyToId: target.replyToId } : {}
1054
+ ...target.replyToId ? { reply_to_id: target.replyToId } : {}
881
1055
  };
882
1056
  try {
883
1057
  await socket.sendMessage(conversation.id, payload);
@@ -1202,106 +1376,10 @@ function BackIcon({ size = 22, color = "currentColor" }) {
1202
1376
  }
1203
1377
  );
1204
1378
  }
1205
-
1206
- // src/core/theme.ts
1207
- var FONT_SIZE = {
1208
- xs: "12px",
1209
- sm: "13px",
1210
- md: "15px",
1211
- lg: "17px",
1212
- xxl: "28px"
1213
- };
1214
- var FONT_WEIGHT = {
1215
- regular: 400,
1216
- medium: 500,
1217
- semibold: 600,
1218
- bold: 700
1219
- };
1220
- var LINE_HEIGHT = {
1221
- tight: 1.3,
1222
- normal: 1.45};
1223
- var SPACE = {
1224
- S1: "4px",
1225
- S2: "8px",
1226
- S3: "12px",
1227
- S4: "16px",
1228
- S5: "20px",
1229
- S6: "24px",
1230
- S12: "48px"
1231
- };
1232
- var RADIUS = {
1233
- sm: "4px",
1234
- md: "8px",
1235
- lg: "12px",
1236
- pill: "9999px",
1237
- full: "50%"
1238
- };
1239
- var COLOR = {
1240
- // Brand
1241
- primary: "#2563eb",
1242
- /** Tinted surface for chips/cards on brand-coloured states. */
1243
- primaryBg: "#dbeafe",
1244
- primaryFg: "#1d4ed8",
1245
- // Neutral grayscale — taken from Tailwind's zinc-leaning slate, the
1246
- // existing dominant family in the package.
1247
- white: "#ffffff",
1248
- neutral50: "#f9fafb",
1249
- neutral100: "#f3f4f6",
1250
- neutral200: "#e5e7eb",
1251
- neutral300: "#d1d5db",
1252
- neutral400: "#9ca3af",
1253
- neutral500: "#6b7280",
1254
- neutral600: "#4b5563",
1255
- neutral700: "#374151",
1256
- neutral800: "#1f2937",
1257
- neutral900: "#111827",
1258
- // Slate (used by day dividers + dialog title bar)
1259
- slate200: "#e2e8f0",
1260
- slate700: "#334155",
1261
- slate800: "#1e293b",
1262
- // Semantic
1263
- success: "#059669",
1264
- warning: "#d97706",
1265
- danger: "#dc2626",
1266
- dangerBg: "#fef2f2",
1267
- dangerBorder: "#fecaca",
1268
- dangerFg: "#b91c1c"
1269
- };
1270
- var SIZE = {
1271
- /** Default interactive height (text buttons, list rows). */
1272
- control: "40px",
1273
- /** Square icon-only buttons inside chrome (popup title, menu trigger). */
1274
- controlIcon: "36px",
1275
- /** Primary input controls (textarea send/attach). 44px = WCAG min. */
1276
- controlPrimary: "44px",
1277
- /** Avatar in the inbox row. */
1278
- avatar: "40px",
1279
- /** Unread dot. */
1280
- dot: "10px"
1281
- };
1282
- var SHADOW = {
1283
- /** Cards / floating popovers (message-actions menu). */
1284
- popover: "0 6px 18px rgba(0, 0, 0, 0.12)",
1285
- /** Dialog (CollabPopup). */
1286
- dialog: "0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08)",
1287
- /** Subtle separator shadow (sticky day divider, toast). */
1288
- subtle: "0 1px 2px rgba(0, 0, 0, 0.04)",
1289
- toast: "0 4px 12px rgba(0, 0, 0, 0.18)"
1290
- };
1291
- var Z_INDEX = {
1292
- /** Sticky day divider — above bubbles, below interactive popovers. */
1293
- sticky: 1,
1294
- /** Toasts inside a panel. */
1295
- toast: 20,
1296
- /** Floating CollabPopup dialog. */
1297
- dialog: 9999,
1298
- /** Portaled message-actions menu — must sit above the dialog. */
1299
- menu: 1e4
1300
- };
1301
1379
  function resolveDisplayName(patientData, displayName) {
1302
1380
  const localComplete = !!patientData.labName && !!patientData.displayOrderId;
1303
1381
  if (localComplete) return buildChannelName(patientData);
1304
- return displayName ?? buildChannelName(patientData);
1382
+ return cleanChannelName(displayName) || buildChannelName(patientData);
1305
1383
  }
1306
1384
  function PatientHeader({
1307
1385
  patientData,
@@ -1314,15 +1392,28 @@ function PatientHeader({
1314
1392
  }) {
1315
1393
  const hasDicom = !!(patientData.studyId && patientData.storageId);
1316
1394
  const meta = [];
1317
- if (patientData.patientAge) meta.push({ label: "Age", value: String(patientData.patientAge) });
1318
- if (patientData.patientSex) meta.push({ label: "Sex", value: String(patientData.patientSex) });
1319
- if (patientData.studyType) meta.push({ label: "Modality", value: patientData.studyType });
1320
- if (patientData.bodyParts && patientData.bodyParts.length > 0) {
1321
- meta.push({ label: "Body part", value: patientData.bodyParts.join(", ") });
1395
+ if (patientData.patientAge && patientData.patientSex) {
1396
+ meta.push({
1397
+ key: "ageSex",
1398
+ label: "Age / Sex",
1399
+ value: `${patientData.patientAge} \xB7 ${patientData.patientSex}`
1400
+ });
1401
+ } else if (patientData.patientAge) {
1402
+ meta.push({ key: "age", label: "Age", value: String(patientData.patientAge) });
1403
+ } else if (patientData.patientSex) {
1404
+ meta.push({ key: "sex", label: "Sex", value: String(patientData.patientSex) });
1405
+ }
1406
+ if (patientData.studyType) {
1407
+ meta.push({ key: "modality", label: "Modality", value: patientData.studyType });
1322
1408
  }
1323
- if (patientData.referringPhysician) {
1324
- meta.push({ label: "Ref", value: patientData.referringPhysician });
1409
+ if (patientData.bodyParts && patientData.bodyParts.length > 0) {
1410
+ meta.push({
1411
+ key: "body",
1412
+ label: "Body part",
1413
+ value: patientData.bodyParts.join(", ")
1414
+ });
1325
1415
  }
1416
+ const hasActions = hasDicom && onOpenDicom || onOpenSettings;
1326
1417
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles.container, children: [
1327
1418
  !hideName && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.nameRow, children: [
1328
1419
  onBack && /* @__PURE__ */ jsxRuntime.jsx(
@@ -1338,54 +1429,55 @@ function PatientHeader({
1338
1429
  ),
1339
1430
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.nameText, children: resolveDisplayName(patientData, displayName) })
1340
1431
  ] }),
1341
- meta.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.metaRow, children: meta.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.metaItem, children: [
1342
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.metaLabel, children: [
1343
- item.label,
1344
- ":"
1345
- ] }),
1346
- " ",
1347
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.metaValue, children: item.value })
1348
- ] }, item.label)) }),
1349
- hasDicom && onOpenDicom || onOpenSettings ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.actions, children: [
1350
- hasDicom && onOpenDicom && /* @__PURE__ */ jsxRuntime.jsxs(
1351
- "button",
1352
- {
1353
- onClick: onOpenDicom,
1354
- style: styles.dicomButton,
1355
- type: "button",
1356
- "aria-label": "View DICOM study",
1357
- children: [
1358
- /* @__PURE__ */ jsxRuntime.jsx(DicomIcon, { size: 18, color: COLOR.white }),
1359
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "View DICOM" })
1360
- ]
1361
- }
1362
- ),
1363
- onOpenSettings && /* @__PURE__ */ jsxRuntime.jsxs(
1364
- "button",
1365
- {
1366
- onClick: onOpenSettings,
1367
- style: styles.settingsButton,
1368
- type: "button",
1369
- "aria-label": "Open channel settings",
1370
- children: [
1371
- /* @__PURE__ */ jsxRuntime.jsx(SettingsIcon, { size: 18, color: COLOR.neutral700 }),
1372
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Settings" })
1373
- ]
1374
- }
1375
- )
1376
- ] }) : null
1432
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.caseCard, children: [
1433
+ meta.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.metaRow, children: meta.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.metaCol, children: [
1434
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.metaLabel, children: item.label }),
1435
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.metaValue, children: item.value })
1436
+ ] }, item.key)) }),
1437
+ hasActions && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.actions, children: [
1438
+ hasDicom && onOpenDicom && /* @__PURE__ */ jsxRuntime.jsxs(
1439
+ "button",
1440
+ {
1441
+ onClick: onOpenDicom,
1442
+ style: styles.dicomButton,
1443
+ type: "button",
1444
+ "aria-label": "View DICOM study",
1445
+ children: [
1446
+ /* @__PURE__ */ jsxRuntime.jsx(DicomIcon, { size: 18, color: COLOR.primary }),
1447
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "View DICOM" })
1448
+ ]
1449
+ }
1450
+ ),
1451
+ onOpenSettings && /* @__PURE__ */ jsxRuntime.jsx(
1452
+ "button",
1453
+ {
1454
+ onClick: onOpenSettings,
1455
+ style: styles.settingsIconButton,
1456
+ type: "button",
1457
+ "aria-label": "Open channel settings",
1458
+ title: "Settings",
1459
+ children: /* @__PURE__ */ jsxRuntime.jsx(SettingsIcon, { size: 18, color: COLOR.neutral600 })
1460
+ }
1461
+ )
1462
+ ] })
1463
+ ] })
1377
1464
  ] });
1378
1465
  }
1379
1466
  var styles = {
1380
1467
  container: {
1381
- padding: `${SPACE.S3} ${SPACE.S4}`,
1382
- borderBottom: `1px solid ${COLOR.neutral200}`,
1383
- backgroundColor: COLOR.neutral50
1468
+ display: "flex",
1469
+ flexDirection: "column",
1470
+ backgroundColor: COLOR.primaryBg,
1471
+ borderBottom: `1px solid ${COLOR.neutral200}`
1384
1472
  },
1473
+ // Optional name row, only when `hideName` is false (compact-inbox path)
1385
1474
  nameRow: {
1386
1475
  display: "flex",
1387
1476
  alignItems: "center",
1388
- gap: SPACE.S2
1477
+ gap: SPACE.S2,
1478
+ padding: `${SPACE.S3} ${SPACE.S4} ${SPACE.S2}`,
1479
+ borderBottom: `1px solid ${COLOR.neutral200}`,
1480
+ backgroundColor: COLOR.white
1389
1481
  },
1390
1482
  backButton: {
1391
1483
  width: SIZE.controlIcon,
@@ -1402,65 +1494,90 @@ var styles = {
1402
1494
  },
1403
1495
  nameText: {
1404
1496
  fontSize: FONT_SIZE.lg,
1405
- fontWeight: FONT_WEIGHT.semibold,
1497
+ fontWeight: FONT_WEIGHT.bold,
1406
1498
  color: COLOR.neutral900,
1407
1499
  lineHeight: LINE_HEIGHT.tight,
1500
+ letterSpacing: "-0.01em",
1408
1501
  minWidth: 0,
1409
1502
  overflow: "hidden",
1410
1503
  textOverflow: "ellipsis",
1411
1504
  whiteSpace: "nowrap"
1412
1505
  },
1506
+ // Case card: meta columns flowing in a wrap-row, actions stacked
1507
+ // below. Each meta column is a small label-above-value pair so the
1508
+ // user can scan field names quickly without a legend.
1509
+ caseCard: {
1510
+ display: "flex",
1511
+ flexDirection: "column",
1512
+ gap: SPACE.S3,
1513
+ padding: `${SPACE.S3} ${SPACE.S5}`
1514
+ },
1515
+ // Wrap-row of stacked label/value columns. Compact column gap keeps
1516
+ // the row dense without crowding; row gap kicks in when columns
1517
+ // wrap to a second line on narrow popups.
1413
1518
  metaRow: {
1414
1519
  display: "flex",
1415
1520
  flexWrap: "wrap",
1416
- columnGap: SPACE.S3,
1417
- rowGap: SPACE.S1,
1418
- marginTop: SPACE.S1
1521
+ columnGap: SPACE.S5,
1522
+ rowGap: SPACE.S2,
1523
+ minWidth: 0
1419
1524
  },
1420
- metaItem: {
1421
- fontSize: FONT_SIZE.sm,
1422
- color: COLOR.neutral700,
1423
- whiteSpace: "nowrap"
1525
+ metaCol: {
1526
+ display: "flex",
1527
+ flexDirection: "column",
1528
+ gap: "2px",
1529
+ minWidth: 0
1424
1530
  },
1425
1531
  metaLabel: {
1532
+ fontSize: "11px",
1533
+ fontWeight: FONT_WEIGHT.bold,
1534
+ letterSpacing: "0.06em",
1535
+ textTransform: "uppercase",
1426
1536
  color: COLOR.neutral500,
1427
- fontWeight: FONT_WEIGHT.medium
1537
+ whiteSpace: "nowrap"
1428
1538
  },
1429
1539
  metaValue: {
1430
- color: COLOR.neutral900
1540
+ fontSize: FONT_SIZE.sm,
1541
+ fontWeight: FONT_WEIGHT.semibold,
1542
+ color: COLOR.neutral900,
1543
+ whiteSpace: "nowrap",
1544
+ overflow: "hidden",
1545
+ textOverflow: "ellipsis",
1546
+ maxWidth: "180px"
1431
1547
  },
1432
1548
  actions: {
1433
1549
  display: "flex",
1434
1550
  gap: SPACE.S2,
1435
- marginTop: SPACE.S3
1551
+ flexShrink: 0
1436
1552
  },
1437
1553
  dicomButton: {
1438
1554
  display: "inline-flex",
1439
1555
  alignItems: "center",
1440
1556
  gap: SPACE.S2,
1441
1557
  minHeight: SIZE.control,
1442
- padding: `${SPACE.S2} ${SPACE.S4}`,
1558
+ padding: `${SPACE.S2} 14px`,
1443
1559
  fontSize: FONT_SIZE.sm,
1444
- fontWeight: FONT_WEIGHT.medium,
1445
- color: COLOR.white,
1446
- backgroundColor: COLOR.primary,
1447
- border: "none",
1448
- borderRadius: RADIUS.md,
1560
+ fontWeight: FONT_WEIGHT.semibold,
1561
+ color: COLOR.primary,
1562
+ backgroundColor: COLOR.white,
1563
+ border: `1.5px solid ${COLOR.primary}`,
1564
+ borderRadius: RADIUS.lg,
1449
1565
  cursor: "pointer"
1450
1566
  },
1451
- settingsButton: {
1567
+ // Settings is icon-only (40x40 tile) since it's a secondary action
1568
+ // and View DICOM already carries a label.
1569
+ settingsIconButton: {
1570
+ width: SIZE.control,
1571
+ height: SIZE.control,
1452
1572
  display: "inline-flex",
1453
1573
  alignItems: "center",
1454
- gap: SPACE.S2,
1455
- minHeight: SIZE.control,
1456
- padding: `${SPACE.S2} ${SPACE.S4}`,
1457
- fontSize: FONT_SIZE.sm,
1458
- fontWeight: FONT_WEIGHT.medium,
1459
- color: COLOR.neutral700,
1574
+ justifyContent: "center",
1460
1575
  backgroundColor: COLOR.white,
1461
- border: `1px solid ${COLOR.neutral300}`,
1462
- borderRadius: RADIUS.md,
1463
- cursor: "pointer"
1576
+ border: `1px solid ${COLOR.neutral200}`,
1577
+ borderRadius: RADIUS.lg,
1578
+ color: COLOR.neutral600,
1579
+ cursor: "pointer",
1580
+ flexShrink: 0
1464
1581
  }
1465
1582
  };
1466
1583
  function ReplyQuoteBlock({
@@ -1469,10 +1586,10 @@ function ReplyQuoteBlock({
1469
1586
  onClick,
1470
1587
  className
1471
1588
  }) {
1472
- const baseBg = inOwnBubble ? "rgba(255,255,255,0.18)" : COLOR.neutral100;
1589
+ const baseBg = inOwnBubble ? "rgba(255,255,255,0.22)" : COLOR.white;
1473
1590
  const barColor = inOwnBubble ? COLOR.white : COLOR.primary;
1474
1591
  const nameColor = inOwnBubble ? COLOR.white : COLOR.primary;
1475
- const bodyColor = inOwnBubble ? "rgba(255,255,255,0.85)" : COLOR.neutral600;
1592
+ const bodyColor = inOwnBubble ? "rgba(255,255,255,0.9)" : COLOR.neutral700;
1476
1593
  const preview = renderSnapshotPreview(snapshot);
1477
1594
  return /* @__PURE__ */ jsxRuntime.jsxs(
1478
1595
  "div",
@@ -1844,43 +1961,50 @@ var styles4 = {
1844
1961
  position: "relative",
1845
1962
  display: "inline-block"
1846
1963
  },
1964
+ // Pill-shaped trigger matching the prototype's `.nc-msg-act` style —
1965
+ // always visible (touch-friendly) rather than hover-only.
1847
1966
  trigger: {
1848
- width: SIZE.controlIcon,
1849
- height: SIZE.controlIcon,
1850
- display: "flex",
1967
+ display: "inline-flex",
1851
1968
  alignItems: "center",
1852
1969
  justifyContent: "center",
1853
- backgroundColor: "transparent",
1854
- border: "none",
1855
- borderRadius: RADIUS.full,
1970
+ width: "32px",
1971
+ height: "32px",
1972
+ backgroundColor: COLOR.white,
1973
+ border: `1px solid ${COLOR.neutral200}`,
1974
+ borderRadius: "999px",
1856
1975
  cursor: "pointer",
1857
- color: COLOR.neutral600
1976
+ color: COLOR.neutral600,
1977
+ transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
1858
1978
  },
1859
1979
  menu: {
1860
1980
  position: "fixed",
1861
- minWidth: "160px",
1981
+ minWidth: "200px",
1862
1982
  backgroundColor: COLOR.white,
1863
- border: `1px solid ${COLOR.neutral200}`,
1864
- borderRadius: RADIUS.md,
1983
+ border: `1px solid ${COLOR.neutral300}`,
1984
+ borderRadius: RADIUS.lg,
1865
1985
  boxShadow: SHADOW.popover,
1866
- padding: SPACE.S1,
1986
+ padding: "6px",
1987
+ display: "flex",
1988
+ flexDirection: "column",
1989
+ gap: "2px",
1867
1990
  // Above the floating popup (z-index dialog) but below any future modal.
1868
1991
  zIndex: Z_INDEX.menu
1869
1992
  },
1870
1993
  item: {
1871
1994
  display: "flex",
1872
1995
  alignItems: "center",
1873
- gap: SPACE.S3,
1996
+ gap: SPACE.S2,
1874
1997
  width: "100%",
1875
1998
  minHeight: SIZE.control,
1876
- padding: `${SPACE.S2} ${SPACE.S3}`,
1999
+ padding: `10px ${SPACE.S3}`,
1877
2000
  fontSize: FONT_SIZE.sm,
1878
- color: COLOR.neutral900,
2001
+ color: COLOR.neutral700,
1879
2002
  backgroundColor: "transparent",
1880
2003
  border: "none",
1881
- borderRadius: RADIUS.sm,
2004
+ borderRadius: RADIUS.md,
1882
2005
  cursor: "pointer",
1883
- textAlign: "left"
2006
+ textAlign: "left",
2007
+ fontFamily: "inherit"
1884
2008
  },
1885
2009
  itemDisabled: {
1886
2010
  color: COLOR.neutral400,
@@ -1994,71 +2118,116 @@ function MessageBubble({
1994
2118
  },
1995
2119
  children: [
1996
2120
  isOwn && actionsSlot,
1997
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles5.bubbleWrapper, children: /* @__PURE__ */ jsxRuntime.jsxs(
1998
- "div",
1999
- {
2000
- style: {
2001
- ...styles5.bubble,
2002
- ...isOwn ? styles5.ownBubble : styles5.otherBubble
2003
- },
2004
- children: [
2005
- message.isPinned && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.pinnedBadge, children: [
2006
- /* @__PURE__ */ jsxRuntime.jsx(PinIcon, { size: 12 }),
2007
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Pinned" })
2008
- ] }),
2009
- message.replyToSnapshot && /* @__PURE__ */ jsxRuntime.jsx(
2010
- ReplyQuoteBlock,
2011
- {
2012
- snapshot: message.replyToSnapshot,
2013
- inOwnBubble: isOwn,
2014
- onClick: onReplyJumpTo
2015
- }
2016
- ),
2017
- !isOwn && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.senderRow, children: [
2018
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.senderName, children: message.senderName }),
2019
- /* @__PURE__ */ jsxRuntime.jsx(RoleBadge, { role: message.senderRole })
2020
- ] }),
2021
- message.type === "text" && /* @__PURE__ */ jsxRuntime.jsx(TextContent, { body: message.body, onDeepLinkClick }),
2022
- message.type === "audio" && /* @__PURE__ */ jsxRuntime.jsx(AudioContent, { mediaUrl: message.mediaUrl, duration: message.mediaDuration }),
2023
- message.type === "image" && /* @__PURE__ */ jsxRuntime.jsx(ImageContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2024
- message.type === "file" && /* @__PURE__ */ jsxRuntime.jsx(FileContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2025
- message.type === "deep_link" && /* @__PURE__ */ jsxRuntime.jsx(DeepLinkContent, { body: message.body, metadata: message.metadata, onDeepLinkClick }),
2026
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles5.timestamp, children: formatTime(message.insertedAt) }),
2027
- isOwn && message.status === "sending" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.statusRow, children: [
2028
- /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 12, color: "rgba(255,255,255,0.85)" }),
2029
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.statusText, children: "Sending\u2026" })
2030
- ] }),
2031
- isOwn && message.status === "failed" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.statusRow, children: [
2032
- /* @__PURE__ */ jsxRuntime.jsx(AlertIcon, { size: 12, color: "#fecaca" }),
2033
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.statusText, children: "Not sent" }),
2034
- onRetry && /* @__PURE__ */ jsxRuntime.jsx(
2035
- "button",
2121
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.bubbleWrapper, children: [
2122
+ !isOwn && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.authorRow, children: [
2123
+ /* @__PURE__ */ jsxRuntime.jsx(
2124
+ "span",
2125
+ {
2126
+ style: {
2127
+ ...styles5.authorAvatar,
2128
+ backgroundColor: roleColor(message.senderRole)
2129
+ },
2130
+ "aria-hidden": "true",
2131
+ children: computeInitials(message.senderName)
2132
+ }
2133
+ ),
2134
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.authorName, children: message.senderName }),
2135
+ /* @__PURE__ */ jsxRuntime.jsx(RoleBadge, { role: message.senderRole })
2136
+ ] }),
2137
+ /* @__PURE__ */ jsxRuntime.jsxs(
2138
+ "div",
2139
+ {
2140
+ style: {
2141
+ ...styles5.bubble,
2142
+ ...isOwn ? styles5.ownBubble : styles5.otherBubble
2143
+ },
2144
+ children: [
2145
+ message.isPinned && /* @__PURE__ */ jsxRuntime.jsxs(
2146
+ "div",
2036
2147
  {
2037
- type: "button",
2038
- onClick: () => onRetry(message.id),
2039
- style: styles5.retryButton,
2040
- "aria-label": "Retry sending message",
2041
- children: "Retry"
2148
+ style: {
2149
+ ...styles5.pinnedBadge,
2150
+ ...isOwn ? styles5.pinnedBadgeOwn : styles5.pinnedBadgeOther
2151
+ },
2152
+ children: [
2153
+ /* @__PURE__ */ jsxRuntime.jsx(PinIcon, { size: 12 }),
2154
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Pinned" })
2155
+ ]
2042
2156
  }
2043
- )
2044
- ] }),
2045
- showSeenBy && isOwn && !message.status && /* @__PURE__ */ jsxRuntime.jsx(
2046
- SeenByIndicator,
2047
- {
2048
- readBy: message.readBy ?? [],
2049
- participants,
2050
- currentUserId,
2051
- senderId: message.senderId
2052
- }
2053
- )
2054
- ]
2055
- }
2056
- ) }),
2157
+ ),
2158
+ message.replyToSnapshot && /* @__PURE__ */ jsxRuntime.jsx(
2159
+ ReplyQuoteBlock,
2160
+ {
2161
+ snapshot: message.replyToSnapshot,
2162
+ inOwnBubble: isOwn,
2163
+ onClick: onReplyJumpTo
2164
+ }
2165
+ ),
2166
+ message.type === "text" && /* @__PURE__ */ jsxRuntime.jsx(TextContent, { body: message.body, onDeepLinkClick }),
2167
+ message.type === "audio" && /* @__PURE__ */ jsxRuntime.jsx(AudioContent, { mediaUrl: message.mediaUrl, duration: message.mediaDuration }),
2168
+ message.type === "image" && /* @__PURE__ */ jsxRuntime.jsx(ImageContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2169
+ message.type === "file" && /* @__PURE__ */ jsxRuntime.jsx(FileContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2170
+ message.type === "deep_link" && /* @__PURE__ */ jsxRuntime.jsx(DeepLinkContent, { body: message.body, metadata: message.metadata, onDeepLinkClick }),
2171
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.bubbleFoot, children: [
2172
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.timestamp, children: formatTime(message.insertedAt) }),
2173
+ isOwn && message.status === "sending" && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles5.footStatus, children: [
2174
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 11, color: "rgba(255,255,255,0.85)" }),
2175
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Sending\u2026" })
2176
+ ] }),
2177
+ isOwn && message.status === "failed" && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles5.footStatusFailed, children: [
2178
+ /* @__PURE__ */ jsxRuntime.jsx(AlertIcon, { size: 11, color: "#fecaca" }),
2179
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Not sent" }),
2180
+ onRetry && /* @__PURE__ */ jsxRuntime.jsx(
2181
+ "button",
2182
+ {
2183
+ type: "button",
2184
+ onClick: () => onRetry(message.id),
2185
+ style: styles5.retryButton,
2186
+ "aria-label": "Retry sending message",
2187
+ children: "Retry"
2188
+ }
2189
+ )
2190
+ ] }),
2191
+ showSeenBy && isOwn && !message.status && /* @__PURE__ */ jsxRuntime.jsx(
2192
+ SeenByIndicator,
2193
+ {
2194
+ readBy: message.readBy ?? [],
2195
+ participants,
2196
+ currentUserId,
2197
+ senderId: message.senderId
2198
+ }
2199
+ )
2200
+ ] })
2201
+ ]
2202
+ }
2203
+ )
2204
+ ] }),
2057
2205
  !isOwn && actionsSlot
2058
2206
  ]
2059
2207
  }
2060
2208
  );
2061
2209
  }
2210
+ function computeInitials(name) {
2211
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]?.toUpperCase() ?? "").join("") || "?";
2212
+ }
2213
+ function roleColor(role) {
2214
+ switch (role) {
2215
+ case "radiologist":
2216
+ return "#4f46e5";
2217
+ // indigo (blue-leaning)
2218
+ case "lab":
2219
+ return "#2563eb";
2220
+ // brand blue
2221
+ case "physician":
2222
+ return "#0284c7";
2223
+ // sky
2224
+ case "admin":
2225
+ return "#64748b";
2226
+ // slate (muted)
2227
+ default:
2228
+ return "#6b7280";
2229
+ }
2230
+ }
2062
2231
  function TextContent({ body, onDeepLinkClick }) {
2063
2232
  if (body.includes(DEEP_LINK_PREFIX)) {
2064
2233
  const parts = body.split(new RegExp(`(${escapeRegex2(DEEP_LINK_PREFIX)}[\\w/.-]+)`, "g"));
@@ -2120,19 +2289,13 @@ function DeepLinkContent({
2120
2289
  );
2121
2290
  }
2122
2291
  function SystemBubble({ message }) {
2123
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.systemMessage, children: [
2292
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles5.systemWrapper, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles5.systemPill, children: [
2124
2293
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: message.body }),
2125
2294
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.systemTime, children: formatTime(message.insertedAt) })
2126
- ] });
2295
+ ] }) });
2127
2296
  }
2128
2297
  function RoleBadge({ role }) {
2129
- const colors = {
2130
- radiologist: "#7c3aed",
2131
- lab: "#2563eb",
2132
- physician: "#059669",
2133
- admin: "#dc2626"
2134
- };
2135
- return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles5.roleBadge, backgroundColor: `${colors[role]}15`, color: colors[role] }, children: ROLE_LABELS[role] ?? role });
2298
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles5.roleBadge, backgroundColor: roleColor(role) }, children: ROLE_LABELS[role] ?? role });
2136
2299
  }
2137
2300
  function formatTime(iso) {
2138
2301
  try {
@@ -2154,89 +2317,134 @@ var styles5 = {
2154
2317
  display: "flex",
2155
2318
  alignItems: "flex-end",
2156
2319
  gap: SPACE.S1,
2157
- marginBottom: SPACE.S2,
2320
+ marginBottom: SPACE.S3,
2158
2321
  paddingLeft: SPACE.S4,
2159
2322
  paddingRight: SPACE.S4
2160
2323
  },
2161
2324
  bubbleWrapper: {
2162
2325
  position: "relative",
2163
- maxWidth: "75%"
2326
+ maxWidth: "78%",
2327
+ display: "flex",
2328
+ flexDirection: "column",
2329
+ gap: "4px"
2164
2330
  },
2165
2331
  actionsSlot: {
2166
2332
  flexShrink: 0,
2167
2333
  alignSelf: "flex-end"
2168
2334
  },
2335
+ // Author row above the bubble — only rendered for others' messages.
2336
+ authorRow: {
2337
+ display: "flex",
2338
+ alignItems: "center",
2339
+ gap: SPACE.S2
2340
+ },
2341
+ authorAvatar: {
2342
+ width: "36px",
2343
+ height: "36px",
2344
+ borderRadius: "50%",
2345
+ color: COLOR.white,
2346
+ fontSize: FONT_SIZE.sm,
2347
+ fontWeight: FONT_WEIGHT.bold,
2348
+ display: "flex",
2349
+ alignItems: "center",
2350
+ justifyContent: "center",
2351
+ flexShrink: 0,
2352
+ letterSpacing: "0.02em"
2353
+ },
2354
+ authorName: {
2355
+ fontSize: FONT_SIZE.sm,
2356
+ fontWeight: FONT_WEIGHT.semibold,
2357
+ color: COLOR.neutral700,
2358
+ overflow: "hidden",
2359
+ textOverflow: "ellipsis",
2360
+ whiteSpace: "nowrap",
2361
+ minWidth: 0
2362
+ },
2169
2363
  bubble: {
2170
2364
  padding: `${SPACE.S3} ${SPACE.S4}`,
2171
- borderRadius: RADIUS.lg,
2172
- wordBreak: "break-word"
2365
+ borderRadius: "18px",
2366
+ wordBreak: "break-word",
2367
+ border: "1px solid transparent"
2173
2368
  },
2174
2369
  ownBubble: {
2175
2370
  backgroundColor: COLOR.primary,
2176
2371
  color: COLOR.white,
2177
- borderBottomRightRadius: RADIUS.sm
2372
+ borderColor: COLOR.primary,
2373
+ borderBottomRightRadius: "6px"
2178
2374
  },
2179
2375
  otherBubble: {
2180
2376
  backgroundColor: COLOR.neutral100,
2181
2377
  color: COLOR.neutral900,
2182
- borderBottomLeftRadius: RADIUS.sm
2378
+ borderColor: COLOR.neutral200,
2379
+ borderBottomLeftRadius: "6px"
2183
2380
  },
2184
2381
  pinnedBadge: {
2185
2382
  display: "inline-flex",
2186
2383
  alignItems: "center",
2187
- gap: SPACE.S1,
2188
- fontSize: FONT_SIZE.xs,
2189
- fontWeight: FONT_WEIGHT.semibold,
2190
- marginBottom: SPACE.S1,
2191
- color: COLOR.neutral500
2384
+ gap: "4px",
2385
+ fontSize: "11px",
2386
+ fontWeight: FONT_WEIGHT.bold,
2387
+ marginBottom: SPACE.S2,
2388
+ padding: `2px ${SPACE.S2}`,
2389
+ borderRadius: "999px",
2390
+ textTransform: "uppercase",
2391
+ letterSpacing: "0.05em"
2192
2392
  },
2193
- senderRow: {
2194
- display: "flex",
2195
- alignItems: "center",
2196
- gap: SPACE.S2,
2197
- marginBottom: SPACE.S1
2393
+ pinnedBadgeOther: {
2394
+ color: "#b8680f",
2395
+ backgroundColor: "#fff7e0"
2198
2396
  },
2199
- senderName: {
2200
- fontSize: FONT_SIZE.sm,
2201
- fontWeight: FONT_WEIGHT.semibold,
2202
- color: COLOR.neutral700
2397
+ pinnedBadgeOwn: {
2398
+ color: "#fff4d6",
2399
+ backgroundColor: "rgba(255, 255, 255, 0.2)"
2203
2400
  },
2204
2401
  roleBadge: {
2205
- fontSize: FONT_SIZE.xs,
2206
- fontWeight: FONT_WEIGHT.semibold,
2207
- padding: `2px ${SPACE.S2}`,
2208
- borderRadius: RADIUS.sm
2402
+ fontSize: "10px",
2403
+ fontWeight: FONT_WEIGHT.bold,
2404
+ padding: "2px 7px",
2405
+ borderRadius: RADIUS.sm,
2406
+ textTransform: "uppercase",
2407
+ letterSpacing: "0.06em",
2408
+ color: COLOR.white,
2409
+ flexShrink: 0
2209
2410
  },
2210
2411
  textBody: {
2211
2412
  margin: 0,
2212
2413
  fontSize: FONT_SIZE.md,
2213
- lineHeight: LINE_HEIGHT.normal
2414
+ lineHeight: 1.5
2415
+ },
2416
+ bubbleFoot: {
2417
+ display: "flex",
2418
+ alignItems: "center",
2419
+ justifyContent: "flex-end",
2420
+ gap: SPACE.S2,
2421
+ marginTop: SPACE.S2,
2422
+ fontSize: "11px",
2423
+ opacity: 0.85
2214
2424
  },
2215
2425
  timestamp: {
2216
- fontSize: FONT_SIZE.xs,
2426
+ fontSize: "11px",
2217
2427
  color: "inherit",
2218
- opacity: 0.8,
2219
- marginTop: SPACE.S1,
2220
- textAlign: "right"
2428
+ fontVariantNumeric: "tabular-nums"
2221
2429
  },
2222
- statusRow: {
2223
- display: "flex",
2430
+ footStatus: {
2431
+ display: "inline-flex",
2224
2432
  alignItems: "center",
2225
- gap: SPACE.S2,
2226
- marginTop: SPACE.S1,
2227
- fontSize: FONT_SIZE.xs,
2228
- justifyContent: "flex-end",
2229
- color: "rgba(255, 255, 255, 0.9)"
2433
+ gap: "4px",
2434
+ fontSize: "11px"
2230
2435
  },
2231
- statusText: {
2232
- opacity: 0.9
2436
+ footStatusFailed: {
2437
+ display: "inline-flex",
2438
+ alignItems: "center",
2439
+ gap: "4px",
2440
+ fontSize: "11px"
2233
2441
  },
2234
2442
  retryButton: {
2235
2443
  background: "transparent",
2236
2444
  border: "none",
2237
2445
  color: COLOR.white,
2238
2446
  fontWeight: FONT_WEIGHT.semibold,
2239
- fontSize: FONT_SIZE.xs,
2447
+ fontSize: "11px",
2240
2448
  textDecoration: "underline",
2241
2449
  cursor: "pointer",
2242
2450
  padding: `0 ${SPACE.S1}`
@@ -2299,18 +2507,25 @@ var styles5 = {
2299
2507
  color: COLOR.neutral500,
2300
2508
  fontFamily: "monospace"
2301
2509
  },
2302
- systemMessage: {
2510
+ systemWrapper: {
2303
2511
  display: "flex",
2304
2512
  justifyContent: "center",
2513
+ margin: `${SPACE.S2} 0`
2514
+ },
2515
+ systemPill: {
2516
+ display: "inline-flex",
2305
2517
  alignItems: "center",
2306
2518
  gap: SPACE.S2,
2307
- padding: `${SPACE.S1} ${SPACE.S4}`,
2308
- fontSize: FONT_SIZE.sm,
2519
+ padding: `5px ${SPACE.S3}`,
2520
+ backgroundColor: COLOR.neutral100,
2521
+ borderRadius: "999px",
2522
+ fontSize: FONT_SIZE.xs,
2309
2523
  color: COLOR.neutral500
2310
2524
  },
2311
2525
  systemTime: {
2312
- fontSize: FONT_SIZE.xs,
2313
- color: COLOR.neutral400
2526
+ fontSize: "11px",
2527
+ color: COLOR.neutral400,
2528
+ fontVariantNumeric: "tabular-nums"
2314
2529
  }
2315
2530
  };
2316
2531
  function ChatIllustration({ size = 96 }) {
@@ -2449,6 +2664,7 @@ var MessageList = React4.forwardRef(function MessageList2({
2449
2664
  const containerRef = React4.useRef(null);
2450
2665
  const bottomRef = React4.useRef(null);
2451
2666
  const prevMessageCount = React4.useRef(messages.length);
2667
+ const hasInitiallyScrolledRef = React4.useRef(false);
2452
2668
  React4.useImperativeHandle(
2453
2669
  ref,
2454
2670
  () => ({
@@ -2466,7 +2682,31 @@ var MessageList = React4.forwardRef(function MessageList2({
2466
2682
  }),
2467
2683
  []
2468
2684
  );
2685
+ React4.useLayoutEffect(() => {
2686
+ const container = containerRef.current;
2687
+ if (!container || messages.length === 0) return;
2688
+ if (hasInitiallyScrolledRef.current) return;
2689
+ hasInitiallyScrolledRef.current = true;
2690
+ prevMessageCount.current = messages.length;
2691
+ const firstUnread = messages.find(
2692
+ (m) => m.type !== "system" && m.senderId !== currentUserId && !(m.readBy ?? []).includes(currentUserId)
2693
+ );
2694
+ if (firstUnread) {
2695
+ const el = container.querySelector(
2696
+ `[data-message-id="${firstUnread.id}"]`
2697
+ );
2698
+ if (el) {
2699
+ const containerRect = container.getBoundingClientRect();
2700
+ const elRect = el.getBoundingClientRect();
2701
+ const offset = elRect.top - containerRect.top + container.scrollTop;
2702
+ container.scrollTop = Math.max(0, offset - 16);
2703
+ return;
2704
+ }
2705
+ }
2706
+ container.scrollTop = container.scrollHeight;
2707
+ }, [messages, currentUserId]);
2469
2708
  React4.useEffect(() => {
2709
+ if (!hasInitiallyScrolledRef.current) return;
2470
2710
  if (messages.length > prevMessageCount.current) {
2471
2711
  const lastMessage = messages[messages.length - 1];
2472
2712
  const isOwnMessage = lastMessage?.senderId === currentUserId;
@@ -2525,7 +2765,11 @@ var MessageList = React4.forwardRef(function MessageList2({
2525
2765
  const showDivider = !!currentBucket && currentBucket !== lastValidBucket;
2526
2766
  if (currentBucket) lastValidBucket = currentBucket;
2527
2767
  return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
2528
- 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) }) }),
2768
+ showDivider && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: [
2769
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayHr, "aria-hidden": "true" }),
2770
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayLabel, children: formatDayDivider(message.insertedAt) }),
2771
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayHr, "aria-hidden": "true" })
2772
+ ] }),
2529
2773
  /* @__PURE__ */ jsxRuntime.jsx("div", { "data-message-id": message.id, "data-natoe-message-bubble": true, style: styles6.bubbleSlot, children: /* @__PURE__ */ jsxRuntime.jsx(
2530
2774
  MessageBubble,
2531
2775
  {
@@ -2624,23 +2868,32 @@ var styles6 = {
2624
2868
  },
2625
2869
  dayDivider: {
2626
2870
  display: "flex",
2627
- justifyContent: "center",
2628
- margin: `${SPACE.S3} 0 ${SPACE.S2}`,
2871
+ alignItems: "center",
2872
+ gap: SPACE.S3,
2873
+ margin: `${SPACE.S4} 0 ${SPACE.S3}`,
2629
2874
  position: "sticky",
2630
2875
  top: SPACE.S1,
2631
2876
  zIndex: Z_INDEX.sticky,
2632
2877
  pointerEvents: "none"
2633
2878
  },
2634
- dayDividerPill: {
2635
- display: "inline-block",
2636
- padding: `${SPACE.S1} ${SPACE.S3}`,
2637
- fontSize: FONT_SIZE.sm,
2638
- fontWeight: FONT_WEIGHT.medium,
2639
- color: COLOR.slate700,
2640
- backgroundColor: COLOR.slate200,
2641
- borderRadius: RADIUS.lg,
2642
- letterSpacing: "0.2px",
2643
- boxShadow: SHADOW.subtle
2879
+ dayHr: {
2880
+ flex: 1,
2881
+ height: "1px",
2882
+ backgroundColor: COLOR.neutral200
2883
+ },
2884
+ dayLabel: {
2885
+ flexShrink: 0,
2886
+ fontSize: FONT_SIZE.xs,
2887
+ fontWeight: FONT_WEIGHT.semibold,
2888
+ color: COLOR.neutral500,
2889
+ textTransform: "uppercase",
2890
+ letterSpacing: "0.06em",
2891
+ // Subtle pill background so the label is readable when it overlays
2892
+ // bubbles passing under it during sticky scroll. Without this the
2893
+ // hairlines visually run through the text on tinted backgrounds.
2894
+ padding: `2px ${SPACE.S2}`,
2895
+ backgroundColor: COLOR.white,
2896
+ borderRadius: RADIUS.sm
2644
2897
  },
2645
2898
  bubbleSlot: {
2646
2899
  animation: "natoe-colab-message-in 180ms ease-out"
@@ -2894,11 +3147,11 @@ function MessageInput({
2894
3147
  {
2895
3148
  onClick: () => fileInputRef.current?.click(),
2896
3149
  disabled: disabled || isSending,
2897
- style: styles8.iconButton,
3150
+ style: styles8.attachButton,
2898
3151
  "aria-label": "Attach file",
2899
3152
  title: "Attach file",
2900
3153
  type: "button",
2901
- children: /* @__PURE__ */ jsxRuntime.jsx(AttachIcon, { size: 22, color: "#374151" })
3154
+ children: /* @__PURE__ */ jsxRuntime.jsx(AttachIcon, { size: 22, color: COLOR.neutral700 })
2902
3155
  }
2903
3156
  ),
2904
3157
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2937,7 +3190,7 @@ function MessageInput({
2937
3190
  style: styles8.textarea
2938
3191
  }
2939
3192
  ),
2940
- /* @__PURE__ */ jsxRuntime.jsx(
3193
+ /* @__PURE__ */ jsxRuntime.jsxs(
2941
3194
  "button",
2942
3195
  {
2943
3196
  onClick: handleSendText,
@@ -2946,10 +3199,13 @@ function MessageInput({
2946
3199
  title: "Send message",
2947
3200
  style: {
2948
3201
  ...styles8.sendButton,
2949
- opacity: text.trim() ? 1 : 0.4
3202
+ opacity: text.trim() ? 1 : 0.5
2950
3203
  },
2951
3204
  type: "button",
2952
- children: /* @__PURE__ */ jsxRuntime.jsx(SendIcon, { size: 20, color: COLOR.white })
3205
+ children: [
3206
+ /* @__PURE__ */ jsxRuntime.jsx(SendIcon, { size: 20, color: COLOR.white }),
3207
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles8.sendLabel, children: "Send" })
3208
+ ]
2953
3209
  }
2954
3210
  )
2955
3211
  ] })
@@ -3011,49 +3267,67 @@ var styles8 = {
3011
3267
  inputBar: {
3012
3268
  display: "flex",
3013
3269
  alignItems: "flex-end",
3014
- gap: SPACE.S2,
3015
- padding: `${SPACE.S3} ${SPACE.S3}`
3016
- },
3017
- iconButton: {
3018
- width: SIZE.controlPrimary,
3019
- height: SIZE.controlPrimary,
3270
+ gap: "10px",
3271
+ padding: `${SPACE.S3} ${SPACE.S4} 14px`
3272
+ },
3273
+ // 48px square tile with a 14px corner radius — visually grounds the
3274
+ // attach affordance as part of the composer chrome rather than a stray
3275
+ // round icon button.
3276
+ attachButton: {
3277
+ width: "48px",
3278
+ height: "48px",
3020
3279
  display: "flex",
3021
3280
  alignItems: "center",
3022
3281
  justifyContent: "center",
3023
- backgroundColor: "transparent",
3024
- border: "none",
3025
- borderRadius: RADIUS.full,
3282
+ backgroundColor: COLOR.neutral100,
3283
+ border: `1.5px solid ${COLOR.neutral200}`,
3284
+ borderRadius: "14px",
3026
3285
  cursor: "pointer",
3027
- flexShrink: 0
3286
+ flexShrink: 0,
3287
+ color: COLOR.neutral700,
3288
+ transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
3028
3289
  },
3029
3290
  textarea: {
3030
3291
  flex: 1,
3031
- padding: `${SPACE.S3} ${SPACE.S4}`,
3292
+ minHeight: "48px",
3293
+ maxHeight: "140px",
3294
+ padding: "13px 16px",
3032
3295
  fontSize: FONT_SIZE.md,
3033
3296
  lineHeight: LINE_HEIGHT.normal,
3034
- border: `1px solid ${COLOR.neutral300}`,
3035
- borderRadius: RADIUS.pill,
3297
+ color: COLOR.neutral900,
3298
+ backgroundColor: COLOR.neutral100,
3299
+ border: `1.5px solid ${COLOR.neutral200}`,
3300
+ borderRadius: "14px",
3036
3301
  resize: "none",
3037
3302
  outline: "none",
3038
3303
  fontFamily: "inherit",
3039
- maxHeight: "120px",
3040
- minHeight: SIZE.controlPrimary
3304
+ transition: "background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease"
3041
3305
  },
3306
+ // Pill-shaped send: icon + "Send" label. Always labelled (never icon-
3307
+ // only) so the affordance is obvious to first-time users.
3042
3308
  sendButton: {
3043
- width: SIZE.controlPrimary,
3044
- height: SIZE.controlPrimary,
3045
- display: "flex",
3309
+ display: "inline-flex",
3046
3310
  alignItems: "center",
3047
- justifyContent: "center",
3311
+ gap: "6px",
3312
+ minWidth: "48px",
3313
+ height: "48px",
3314
+ padding: `0 ${SPACE.S4}`,
3048
3315
  backgroundColor: COLOR.primary,
3049
3316
  color: COLOR.white,
3050
3317
  border: "none",
3051
- borderRadius: RADIUS.full,
3318
+ borderRadius: "14px",
3052
3319
  cursor: "pointer",
3053
- flexShrink: 0
3054
- }};
3055
- function ParticipantsList({
3056
- participants,
3320
+ flexShrink: 0,
3321
+ fontFamily: "inherit",
3322
+ fontSize: "15px",
3323
+ fontWeight: FONT_WEIGHT.semibold,
3324
+ transition: "opacity 120ms ease, background-color 120ms ease"
3325
+ },
3326
+ sendLabel: {
3327
+ lineHeight: 1
3328
+ }};
3329
+ function ParticipantsList({
3330
+ participants,
3057
3331
  currentUserId,
3058
3332
  isAdmin,
3059
3333
  onRemoveUser,
@@ -3107,10 +3381,14 @@ function fallbackName(role) {
3107
3381
  }
3108
3382
  function RoleBadge2({ role }) {
3109
3383
  const colors = {
3110
- radiologist: { bg: "#f5f3ff", text: "#7c3aed" },
3384
+ radiologist: { bg: "#eef2ff", text: "#4f46e5" },
3385
+ // indigo
3111
3386
  lab: { bg: "#eff6ff", text: "#2563eb" },
3112
- physician: { bg: "#ecfdf5", text: "#059669" },
3113
- admin: { bg: "#fef2f2", text: "#dc2626" }
3387
+ // brand blue
3388
+ physician: { bg: "#e0f2fe", text: "#0284c7" },
3389
+ // sky
3390
+ admin: { bg: "#f1f5f9", text: "#64748b" }
3391
+ // slate
3114
3392
  };
3115
3393
  const color = colors[role];
3116
3394
  return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles9.roleBadge, backgroundColor: color.bg, color: color.text }, children: role });
@@ -3719,45 +3997,6 @@ var styles12 = {
3719
3997
  letterSpacing: "0.4px"
3720
3998
  }
3721
3999
  };
3722
-
3723
- // src/core/styles.ts
3724
- var FONT_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
3725
- var ROOT_CLASS = "natoe-colab-root";
3726
- var GLOBAL_STYLE_ID = "natoe-colab-global-styles";
3727
- var GLOBAL_CSS = `
3728
- @keyframes natoe-colab-spin { to { transform: rotate(360deg); } }
3729
-
3730
- @keyframes natoe-colab-message-in {
3731
- from { opacity: 0; transform: translateY(4px); }
3732
- to { opacity: 1; transform: translateY(0); }
3733
- }
3734
-
3735
- /* High-contrast adjustments \u2014 older users on OS-level high-contrast mode
3736
- get heavier borders and stronger text without losing the package look. */
3737
- @media (prefers-contrast: more) {
3738
- .${ROOT_CLASS} {
3739
- color: #000000;
3740
- }
3741
- .${ROOT_CLASS} button {
3742
- outline: 1px solid currentColor;
3743
- }
3744
- }
3745
-
3746
- /* Honour reduced-motion preferences by killing entry animations. */
3747
- @media (prefers-reduced-motion: reduce) {
3748
- .${ROOT_CLASS} [data-natoe-message-bubble] {
3749
- animation: none !important;
3750
- }
3751
- }
3752
- `;
3753
- function ensureGlobalStyles() {
3754
- if (typeof document === "undefined") return;
3755
- if (document.getElementById(GLOBAL_STYLE_ID)) return;
3756
- const style = document.createElement("style");
3757
- style.id = GLOBAL_STYLE_ID;
3758
- style.textContent = GLOBAL_CSS;
3759
- document.head.appendChild(style);
3760
- }
3761
4000
  ensureGlobalStyles();
3762
4001
  function CollabPanel({
3763
4002
  orderId,
@@ -3766,6 +4005,7 @@ function CollabPanel({
3766
4005
  showSeenBy = true,
3767
4006
  onBack,
3768
4007
  hidePatientName = false,
4008
+ onConversationChange,
3769
4009
  className,
3770
4010
  style
3771
4011
  }) {
@@ -3794,6 +4034,9 @@ function CollabPanel({
3794
4034
  pinMessage,
3795
4035
  unpinMessage
3796
4036
  } = useConversation({ orderId, patientData, participantIds });
4037
+ React4.useEffect(() => {
4038
+ onConversationChange?.(conversation);
4039
+ }, [conversation, onConversationChange]);
3797
4040
  const channelSettings = useChannelSettings({
3798
4041
  conversationId: conversation?.id ?? null
3799
4042
  });
@@ -4002,6 +4245,7 @@ function CollabPopup({
4002
4245
  isOpen,
4003
4246
  onClose,
4004
4247
  onBack,
4248
+ onMinimize,
4005
4249
  initialPosition,
4006
4250
  width = 380,
4007
4251
  height = 520,
@@ -4015,10 +4259,12 @@ function CollabPopup({
4015
4259
  );
4016
4260
  const [isDragging, setIsDragging] = React4.useState(false);
4017
4261
  const [isMinimized, setIsMinimized] = React4.useState(false);
4262
+ const [loadedConversation, setLoadedConversation] = React4.useState(null);
4018
4263
  const dragOffset = React4.useRef({ x: 0, y: 0 });
4019
4264
  const containerRef = React4.useRef(null);
4020
4265
  const previouslyFocused = React4.useRef(null);
4021
4266
  const titleId = React4.useId();
4267
+ const titleText = cleanChannelName(loadedConversation?.name) || buildChannelName(patientData);
4022
4268
  const handleMouseDown = React4.useCallback(
4023
4269
  (e) => {
4024
4270
  if (!e.target.closest("[data-drag-handle]")) return;
@@ -4115,40 +4361,62 @@ function CollabPopup({
4115
4361
  "button",
4116
4362
  {
4117
4363
  onClick: onBack,
4118
- style: styles13.titleButton,
4119
- "aria-label": "Go back",
4364
+ style: styles13.titleSquare,
4365
+ "aria-label": "Back to messages",
4120
4366
  title: "Back",
4121
4367
  type: "button",
4122
- children: "\u2190"
4368
+ children: /* @__PURE__ */ jsxRuntime.jsx(BackIcon, { size: 18, color: COLOR.neutral700 })
4123
4369
  }
4124
4370
  ),
4125
- /* @__PURE__ */ jsxRuntime.jsx("span", { id: titleId, style: { ...styles13.titleText, flex: 1 }, children: buildChannelName(patientData) }),
4371
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles13.titleCenter, children: [
4372
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, style: styles13.patientName, children: patientData.patientName || titleText }),
4373
+ (patientData.labName || patientData.displayOrderId) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles13.subRow, children: [
4374
+ patientData.labName && /* @__PURE__ */ jsxRuntime.jsx("span", { children: patientData.labName }),
4375
+ patientData.labName && patientData.displayOrderId && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles13.subDot, "aria-hidden": "true", children: "\xB7" }),
4376
+ patientData.displayOrderId && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles13.mono, children: [
4377
+ "#",
4378
+ patientData.displayOrderId.slice(-4)
4379
+ ] })
4380
+ ] })
4381
+ ] }),
4126
4382
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles13.titleActions, children: [
4127
4383
  /* @__PURE__ */ jsxRuntime.jsx(
4128
4384
  "button",
4129
4385
  {
4130
- onClick: () => setIsMinimized(!isMinimized),
4131
- style: styles13.titleButton,
4132
- "aria-label": isMinimized ? "Expand chat" : "Minimize chat",
4133
- title: isMinimized ? "Expand" : "Minimize",
4386
+ onClick: () => {
4387
+ if (onMinimize) onMinimize();
4388
+ else setIsMinimized(!isMinimized);
4389
+ },
4390
+ style: styles13.titleSquare,
4391
+ "aria-label": onMinimize ? "Minimize chat" : isMinimized ? "Expand chat" : "Minimize chat",
4392
+ title: onMinimize ? "Minimize" : isMinimized ? "Expand" : "Minimize",
4134
4393
  type: "button",
4135
- children: isMinimized ? /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, { size: 16, color: COLOR.slate200 }) : /* @__PURE__ */ jsxRuntime.jsx(MinimizeIcon, { size: 16, color: COLOR.slate200 })
4394
+ children: onMinimize || !isMinimized ? /* @__PURE__ */ jsxRuntime.jsx(MinimizeIcon, { size: 18, color: COLOR.neutral700 }) : /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, { size: 18, color: COLOR.neutral700 })
4136
4395
  }
4137
4396
  ),
4138
4397
  /* @__PURE__ */ jsxRuntime.jsx(
4139
4398
  "button",
4140
4399
  {
4141
4400
  onClick: onClose,
4142
- style: styles13.titleButton,
4401
+ style: styles13.titleSquare,
4143
4402
  "aria-label": "Close chat",
4144
4403
  title: "Close",
4145
4404
  type: "button",
4146
- children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { size: 16, color: COLOR.slate200 })
4405
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { size: 18, color: COLOR.neutral700 })
4147
4406
  }
4148
4407
  )
4149
4408
  ] })
4150
4409
  ] }),
4151
- !isMinimized && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles13.panelWrapper, children: /* @__PURE__ */ jsxRuntime.jsx(CollabPanel, { orderId, patientData, participantIds, hidePatientName: true }) })
4410
+ !isMinimized && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles13.panelWrapper, children: /* @__PURE__ */ jsxRuntime.jsx(
4411
+ CollabPanel,
4412
+ {
4413
+ orderId,
4414
+ patientData,
4415
+ participantIds,
4416
+ hidePatientName: true,
4417
+ onConversationChange: setLoadedConversation
4418
+ }
4419
+ ) })
4152
4420
  ]
4153
4421
  }
4154
4422
  );
@@ -4157,51 +4425,78 @@ var styles13 = {
4157
4425
  container: {
4158
4426
  position: "fixed",
4159
4427
  zIndex: Z_INDEX.dialog,
4160
- borderRadius: RADIUS.lg,
4161
- boxShadow: SHADOW.dialog,
4428
+ borderRadius: "20px",
4429
+ boxShadow: "0 20px 48px rgba(20, 21, 28, 0.16), 0 6px 16px rgba(20, 21, 28, 0.08)",
4162
4430
  overflow: "hidden",
4163
4431
  display: "flex",
4164
4432
  flexDirection: "column",
4165
4433
  backgroundColor: COLOR.white,
4166
- border: `1px solid ${COLOR.neutral200}`,
4434
+ border: `1px solid ${COLOR.neutral300}`,
4167
4435
  transition: "height 0.2s ease",
4168
4436
  fontFamily: FONT_STACK
4169
4437
  },
4170
4438
  titleBar: {
4171
4439
  display: "flex",
4172
- justifyContent: "space-between",
4173
4440
  alignItems: "center",
4174
- padding: `0 ${SPACE.S2} 0 ${SPACE.S4}`,
4175
- height: "52px",
4176
- backgroundColor: COLOR.slate800,
4441
+ gap: SPACE.S2,
4442
+ padding: `10px ${SPACE.S3}`,
4443
+ backgroundColor: COLOR.white,
4444
+ borderBottom: `1px solid ${COLOR.neutral200}`,
4177
4445
  cursor: "grab",
4178
4446
  userSelect: "none",
4179
4447
  flexShrink: 0
4180
4448
  },
4181
- titleText: {
4182
- fontSize: FONT_SIZE.md,
4183
- fontWeight: FONT_WEIGHT.medium,
4184
- color: COLOR.white,
4449
+ titleCenter: {
4450
+ flex: 1,
4451
+ minWidth: 0,
4452
+ display: "flex",
4453
+ flexDirection: "column",
4454
+ gap: "2px"
4455
+ },
4456
+ patientName: {
4457
+ margin: 0,
4458
+ fontSize: FONT_SIZE.lg,
4459
+ fontWeight: FONT_WEIGHT.bold,
4460
+ letterSpacing: "-0.01em",
4461
+ color: COLOR.neutral900,
4462
+ overflow: "hidden",
4463
+ textOverflow: "ellipsis",
4464
+ whiteSpace: "nowrap"
4465
+ },
4466
+ subRow: {
4467
+ display: "flex",
4468
+ alignItems: "center",
4469
+ gap: SPACE.S2,
4470
+ fontSize: FONT_SIZE.sm,
4471
+ color: COLOR.neutral500,
4185
4472
  overflow: "hidden",
4186
4473
  textOverflow: "ellipsis",
4187
4474
  whiteSpace: "nowrap"
4188
4475
  },
4476
+ subDot: {
4477
+ color: COLOR.neutral400
4478
+ },
4479
+ mono: {
4480
+ fontFamily: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
4481
+ fontVariantNumeric: "tabular-nums"
4482
+ },
4189
4483
  titleActions: {
4190
4484
  display: "flex",
4191
4485
  gap: SPACE.S1,
4192
4486
  flexShrink: 0
4193
4487
  },
4194
- titleButton: {
4195
- width: SIZE.control,
4196
- height: SIZE.control,
4488
+ titleSquare: {
4489
+ width: "36px",
4490
+ height: "36px",
4197
4491
  display: "flex",
4198
4492
  alignItems: "center",
4199
4493
  justifyContent: "center",
4200
- backgroundColor: "transparent",
4201
- border: "none",
4494
+ backgroundColor: COLOR.neutral100,
4495
+ border: `1px solid ${COLOR.neutral200}`,
4202
4496
  borderRadius: RADIUS.md,
4203
4497
  cursor: "pointer",
4204
- color: COLOR.slate200
4498
+ color: COLOR.neutral700,
4499
+ flexShrink: 0
4205
4500
  },
4206
4501
  panelWrapper: {
4207
4502
  flex: 1,
@@ -4505,10 +4800,14 @@ function InlineMessageRow({ message }) {
4505
4800
  }
4506
4801
  function RoleDot({ role }) {
4507
4802
  const colors = {
4508
- radiologist: "#7c3aed",
4803
+ radiologist: "#4f46e5",
4804
+ // indigo
4509
4805
  lab: "#2563eb",
4510
- physician: "#059669",
4511
- admin: "#dc2626"
4806
+ // brand blue
4807
+ physician: "#0284c7",
4808
+ // sky
4809
+ admin: "#64748b"
4810
+ // slate
4512
4811
  };
4513
4812
  return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.roleDot, backgroundColor: colors[role] } });
4514
4813
  }
@@ -4930,6 +5229,10 @@ function ConversationListItem({
4930
5229
  }) {
4931
5230
  const hasUnread = item.unreadCount > 0;
4932
5231
  const displayName = item.name || item.patientSnapshot?.patientName || "Unknown";
5232
+ const studyType = item.patientSnapshot?.studyType;
5233
+ const patientId = item.patientSnapshot?.patientId;
5234
+ const showStudyRow = !!(studyType || patientId);
5235
+ const initials = computeInitials2(item.patientSnapshot?.patientName || displayName);
4933
5236
  return /* @__PURE__ */ jsxRuntime.jsxs(
4934
5237
  "button",
4935
5238
  {
@@ -4937,15 +5240,11 @@ function ConversationListItem({
4937
5240
  onClick,
4938
5241
  style: {
4939
5242
  ...styles15.container,
4940
- ...isSelected ? styles15.selected : {},
4941
- ...hasUnread ? styles15.unread : {}
5243
+ ...isSelected ? styles15.selected : {}
4942
5244
  },
4943
5245
  type: "button",
4944
5246
  children: [
4945
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles15.avatarWrapper, children: [
4946
- item.picture ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.picture, alt: "", style: styles15.avatar }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles15.avatarFallback, children: (displayName).charAt(0).toUpperCase() }),
4947
- hasUnread && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.unreadDot })
4948
- ] }),
5247
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles15.avatarWrapper, children: item.picture ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.picture, alt: "", style: styles15.avatarImg }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles15.avatarTile, "aria-hidden": "true", children: initials }) }),
4949
5248
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles15.content, children: [
4950
5249
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles15.topRow, children: [
4951
5250
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4969,101 +5268,120 @@ function ConversationListItem({
4969
5268
  }
4970
5269
  )
4971
5270
  ] }),
5271
+ showStudyRow && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles15.studyRow, children: [
5272
+ studyType && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.studyType, children: studyType }),
5273
+ studyType && patientId && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.studyDivider, children: "\xB7" }),
5274
+ patientId && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles15.studyId, children: [
5275
+ "MRN ",
5276
+ patientId
5277
+ ] })
5278
+ ] }),
4972
5279
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles15.bottomRow, children: [
4973
- /* @__PURE__ */ jsxRuntime.jsx(
4974
- "span",
4975
- {
4976
- style: {
4977
- ...styles15.preview,
4978
- ...hasUnread ? styles15.previewUnread : {}
4979
- },
4980
- children: renderLastMessagePreview(item.lastMessage)
4981
- }
4982
- ),
4983
- hasUnread && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.badge, children: item.unreadCount > 99 ? "99+" : item.unreadCount })
5280
+ /* @__PURE__ */ jsxRuntime.jsx(PreviewLine, { message: item.lastMessage, hasUnread }),
5281
+ hasUnread && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.badge, "aria-label": `${item.unreadCount} unread`, children: item.unreadCount > 99 ? "99+" : item.unreadCount })
4984
5282
  ] })
4985
5283
  ] })
4986
5284
  ]
4987
5285
  }
4988
5286
  );
4989
5287
  }
4990
- function renderLastMessagePreview(message) {
4991
- if (!message) return "No messages yet";
4992
- const prefix = message.senderName ? `${message.senderName}: ` : "";
5288
+ function PreviewLine({ message, hasUnread }) {
5289
+ if (!message) {
5290
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.preview, children: "No messages yet" });
5291
+ }
5292
+ if (message.type === "system") {
5293
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.preview, children: message.body });
5294
+ }
5295
+ const text = previewText(message);
5296
+ const sender = message.senderName ? `${message.senderName.split(" ")[0]}:` : "";
5297
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5298
+ "span",
5299
+ {
5300
+ style: {
5301
+ ...styles15.preview,
5302
+ ...hasUnread ? styles15.previewUnread : {}
5303
+ },
5304
+ children: [
5305
+ sender && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles15.previewSender, children: [
5306
+ sender,
5307
+ " "
5308
+ ] }),
5309
+ text
5310
+ ]
5311
+ }
5312
+ );
5313
+ }
5314
+ function previewText(message) {
4993
5315
  switch (message.type) {
4994
5316
  case "audio":
4995
- return `${prefix}\u{1F3A4} Voice message`;
5317
+ return "Voice message";
4996
5318
  case "image":
4997
- return `${prefix}\u{1F4F7} ${message.fileName || "Image"}`;
5319
+ return message.fileName || "Image";
4998
5320
  case "file":
4999
- return `${prefix}\u{1F4CE} ${message.fileName || "File"}`;
5321
+ return message.fileName || "File";
5000
5322
  case "deep_link":
5001
- return `${prefix}\u{1F517} ${message.body}`;
5002
- case "system":
5003
- return message.body;
5323
+ return message.body || "Shared link";
5004
5324
  default: {
5005
5325
  const body = message.body || "";
5006
- const preview = body.length > 60 ? `${body.slice(0, 60)}\u2026` : body;
5007
- return `${prefix}${preview}`;
5326
+ return body.length > 60 ? `${body.slice(0, 60)}\u2026` : body;
5008
5327
  }
5009
5328
  }
5010
5329
  }
5330
+ function computeInitials2(name) {
5331
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("") || "?";
5332
+ }
5011
5333
  var styles15 = {
5012
5334
  container: {
5013
5335
  display: "flex",
5014
- alignItems: "center",
5015
- gap: SPACE.S3,
5016
- padding: `${SPACE.S3} ${SPACE.S3}`,
5336
+ alignItems: "flex-start",
5337
+ gap: "14px",
5017
5338
  width: "100%",
5339
+ padding: `14px ${SPACE.S5}`,
5340
+ minHeight: "84px",
5018
5341
  backgroundColor: "transparent",
5019
5342
  border: "none",
5020
- borderBottom: `1px solid ${COLOR.neutral100}`,
5343
+ borderLeft: "4px solid transparent",
5021
5344
  cursor: "pointer",
5022
- textAlign: "left"
5345
+ textAlign: "left",
5346
+ color: "inherit",
5347
+ fontFamily: "inherit",
5348
+ transition: "background-color 120ms ease"
5023
5349
  },
5024
5350
  selected: {
5025
- backgroundColor: COLOR.primaryBg
5351
+ backgroundColor: COLOR.primaryBg,
5352
+ borderLeftColor: COLOR.primary
5026
5353
  },
5027
- unread: {},
5028
5354
  avatarWrapper: {
5029
- position: "relative",
5030
- width: SIZE.avatar,
5031
- height: SIZE.avatar,
5355
+ width: "52px",
5356
+ height: "52px",
5032
5357
  flexShrink: 0
5033
5358
  },
5034
- avatar: {
5035
- width: SIZE.avatar,
5036
- height: SIZE.avatar,
5037
- borderRadius: RADIUS.full,
5038
- objectFit: "cover"
5359
+ avatarImg: {
5360
+ width: "52px",
5361
+ height: "52px",
5362
+ borderRadius: RADIUS.xl,
5363
+ objectFit: "cover",
5364
+ boxShadow: "inset 0 -2px 0 rgba(0, 0, 0, 0.08)"
5039
5365
  },
5040
- avatarFallback: {
5041
- width: SIZE.avatar,
5042
- height: SIZE.avatar,
5043
- borderRadius: RADIUS.full,
5044
- backgroundColor: COLOR.neutral200,
5366
+ avatarTile: {
5367
+ width: "52px",
5368
+ height: "52px",
5369
+ borderRadius: RADIUS.xl,
5370
+ backgroundColor: COLOR.primary,
5371
+ color: COLOR.white,
5045
5372
  display: "flex",
5046
5373
  alignItems: "center",
5047
5374
  justifyContent: "center",
5048
- fontSize: FONT_SIZE.md,
5049
- fontWeight: FONT_WEIGHT.semibold,
5050
- color: COLOR.neutral500
5051
- },
5052
- unreadDot: {
5053
- position: "absolute",
5054
- top: "0",
5055
- left: "0",
5056
- width: SIZE.dot,
5057
- height: SIZE.dot,
5058
- borderRadius: RADIUS.full,
5059
- backgroundColor: COLOR.primary,
5060
- border: `2px solid ${COLOR.white}`
5375
+ fontSize: FONT_SIZE.lg,
5376
+ fontWeight: FONT_WEIGHT.bold,
5377
+ letterSpacing: "0.02em",
5378
+ boxShadow: "inset 0 -2px 0 rgba(0, 0, 0, 0.08)"
5061
5379
  },
5062
5380
  content: {
5063
5381
  flex: 1,
5064
5382
  display: "flex",
5065
5383
  flexDirection: "column",
5066
- gap: "2px",
5384
+ gap: "3px",
5067
5385
  minWidth: 0
5068
5386
  },
5069
5387
  topRow: {
@@ -5088,123 +5406,191 @@ var styles15 = {
5088
5406
  time: {
5089
5407
  fontSize: FONT_SIZE.xs,
5090
5408
  color: COLOR.neutral500,
5409
+ fontVariantNumeric: "tabular-nums",
5091
5410
  flexShrink: 0
5092
5411
  },
5093
5412
  timeUnread: {
5094
5413
  color: COLOR.primary,
5095
5414
  fontWeight: FONT_WEIGHT.semibold
5096
5415
  },
5416
+ studyRow: {
5417
+ display: "flex",
5418
+ alignItems: "center",
5419
+ gap: SPACE.S2,
5420
+ minWidth: 0
5421
+ },
5422
+ studyType: {
5423
+ fontSize: FONT_SIZE.sm,
5424
+ color: COLOR.neutral500,
5425
+ overflow: "hidden",
5426
+ textOverflow: "ellipsis",
5427
+ whiteSpace: "nowrap"
5428
+ },
5429
+ studyDivider: {
5430
+ fontSize: FONT_SIZE.sm,
5431
+ color: COLOR.neutral400,
5432
+ flexShrink: 0
5433
+ },
5434
+ studyId: {
5435
+ fontSize: FONT_SIZE.sm,
5436
+ color: COLOR.neutral500,
5437
+ fontFamily: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
5438
+ fontVariantNumeric: "tabular-nums",
5439
+ flexShrink: 0
5440
+ },
5097
5441
  bottomRow: {
5098
5442
  display: "flex",
5099
5443
  justifyContent: "space-between",
5100
5444
  alignItems: "center",
5101
- gap: SPACE.S2
5445
+ gap: SPACE.S2,
5446
+ marginTop: "2px"
5102
5447
  },
5103
5448
  preview: {
5104
5449
  fontSize: FONT_SIZE.sm,
5105
- color: COLOR.neutral600,
5450
+ color: COLOR.neutral500,
5106
5451
  overflow: "hidden",
5107
5452
  textOverflow: "ellipsis",
5108
5453
  whiteSpace: "nowrap",
5109
5454
  flex: 1,
5110
5455
  minWidth: 0
5111
5456
  },
5457
+ previewSender: {
5458
+ color: COLOR.neutral700,
5459
+ fontWeight: FONT_WEIGHT.semibold
5460
+ },
5112
5461
  previewUnread: {
5113
5462
  color: COLOR.neutral900,
5114
5463
  fontWeight: FONT_WEIGHT.medium
5115
5464
  },
5116
5465
  badge: {
5117
- minWidth: "20px",
5118
- height: "20px",
5119
- padding: `0 ${SPACE.S2}`,
5466
+ minWidth: "22px",
5467
+ height: "22px",
5468
+ padding: "0 9px",
5120
5469
  fontSize: FONT_SIZE.xs,
5121
- fontWeight: FONT_WEIGHT.semibold,
5470
+ fontWeight: FONT_WEIGHT.bold,
5122
5471
  color: COLOR.white,
5123
- backgroundColor: COLOR.primary,
5472
+ backgroundColor: COLOR.danger,
5124
5473
  borderRadius: RADIUS.pill,
5125
5474
  display: "flex",
5126
5475
  alignItems: "center",
5127
5476
  justifyContent: "center",
5477
+ fontVariantNumeric: "tabular-nums",
5128
5478
  flexShrink: 0
5129
5479
  }
5130
5480
  };
5481
+ function SearchIcon({ size = 20, color = "currentColor" }) {
5482
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5483
+ "svg",
5484
+ {
5485
+ xmlns: "http://www.w3.org/2000/svg",
5486
+ width: size,
5487
+ height: size,
5488
+ viewBox: "0 0 24 24",
5489
+ fill: "none",
5490
+ stroke: color,
5491
+ strokeWidth: "2",
5492
+ strokeLinecap: "round",
5493
+ strokeLinejoin: "round",
5494
+ "aria-hidden": "true",
5495
+ children: [
5496
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
5497
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 20l-4-4" })
5498
+ ]
5499
+ }
5500
+ );
5501
+ }
5131
5502
  function ConversationList({
5132
5503
  conversations,
5133
5504
  selectedId,
5134
5505
  isLoading,
5135
5506
  error,
5136
5507
  onSelect,
5137
- title = "Conversations",
5138
5508
  className
5139
5509
  }) {
5140
5510
  const [query, setQuery] = React4.useState("");
5141
- const [showUnreadOnly, setShowUnreadOnly] = React4.useState(false);
5511
+ const [filter, setFilter] = React4.useState("all");
5512
+ const [searchFocused, setSearchFocused] = React4.useState(false);
5513
+ const unreadCount = React4.useMemo(
5514
+ () => conversations.filter((c) => c.unreadCount > 0).length,
5515
+ [conversations]
5516
+ );
5517
+ const autoDefaultedRef = React4.useRef(false);
5518
+ React4.useEffect(() => {
5519
+ if (autoDefaultedRef.current) return;
5520
+ if (conversations.length === 0) return;
5521
+ autoDefaultedRef.current = true;
5522
+ if (unreadCount > 0) setFilter("unread");
5523
+ }, [conversations.length, unreadCount]);
5142
5524
  const filtered = React4.useMemo(() => {
5143
5525
  let result = conversations;
5144
- if (showUnreadOnly) {
5526
+ if (filter === "unread") {
5145
5527
  result = result.filter((c) => c.unreadCount > 0);
5146
5528
  }
5147
5529
  const q = query.trim().toLowerCase();
5148
5530
  if (q) {
5149
5531
  result = result.filter((c) => {
5150
- const patient = c.patientSnapshot?.patientName?.toLowerCase() || "";
5151
5532
  const name = c.name.toLowerCase();
5152
- return name.includes(q) || patient.includes(q);
5533
+ const patient = c.patientSnapshot?.patientName?.toLowerCase() || "";
5534
+ const studyType = c.patientSnapshot?.studyType?.toLowerCase() || "";
5535
+ const patientId = c.patientSnapshot?.patientId?.toLowerCase() || "";
5536
+ return name.includes(q) || patient.includes(q) || studyType.includes(q) || patientId.includes(q);
5153
5537
  });
5154
5538
  }
5155
5539
  return result;
5156
- }, [conversations, query, showUnreadOnly]);
5157
- const totalUnread = conversations.reduce((sum, c) => sum + c.unreadCount, 0);
5540
+ }, [conversations, query, filter]);
5158
5541
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles16.container, children: [
5159
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.header, children: [
5160
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.titleRow, children: [
5161
- /* @__PURE__ */ jsxRuntime.jsx("h3", { style: styles16.title, children: title }),
5162
- totalUnread > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles16.totalBadge, children: totalUnread })
5163
- ] }),
5164
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles16.searchRow, children: /* @__PURE__ */ jsxRuntime.jsx(
5165
- "input",
5542
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.tabs, children: [
5543
+ /* @__PURE__ */ jsxRuntime.jsx(
5544
+ TabButton,
5166
5545
  {
5167
- type: "text",
5168
- value: query,
5169
- onChange: (e) => setQuery(e.target.value),
5170
- placeholder: "Search patients or channels...",
5171
- style: styles16.searchInput
5546
+ label: "Unread",
5547
+ count: unreadCount,
5548
+ isActive: filter === "unread",
5549
+ onClick: () => setFilter("unread")
5172
5550
  }
5173
- ) }),
5174
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.filterRow, children: [
5175
- /* @__PURE__ */ jsxRuntime.jsx(
5176
- "button",
5177
- {
5178
- onClick: () => setShowUnreadOnly(false),
5179
- style: {
5180
- ...styles16.filterButton,
5181
- ...!showUnreadOnly ? styles16.filterButtonActive : {}
5182
- },
5183
- type: "button",
5184
- children: "All"
5185
- }
5186
- ),
5187
- /* @__PURE__ */ jsxRuntime.jsxs(
5188
- "button",
5189
- {
5190
- onClick: () => setShowUnreadOnly(true),
5191
- style: {
5192
- ...styles16.filterButton,
5193
- ...showUnreadOnly ? styles16.filterButtonActive : {}
5194
- },
5195
- type: "button",
5196
- children: [
5197
- "Unread ",
5198
- totalUnread > 0 && `(${totalUnread})`
5199
- ]
5200
- }
5201
- )
5202
- ] })
5551
+ ),
5552
+ /* @__PURE__ */ jsxRuntime.jsx(
5553
+ TabButton,
5554
+ {
5555
+ label: "All",
5556
+ count: conversations.length,
5557
+ isActive: filter === "all",
5558
+ onClick: () => setFilter("all")
5559
+ }
5560
+ )
5203
5561
  ] }),
5562
+ /* @__PURE__ */ jsxRuntime.jsxs(
5563
+ "div",
5564
+ {
5565
+ style: {
5566
+ ...styles16.search,
5567
+ ...searchFocused ? styles16.searchFocused : {}
5568
+ },
5569
+ children: [
5570
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles16.searchIcon, "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(SearchIcon, { size: 20, color: COLOR.neutral500 }) }),
5571
+ /* @__PURE__ */ jsxRuntime.jsx(
5572
+ "input",
5573
+ {
5574
+ type: "text",
5575
+ value: query,
5576
+ onChange: (e) => setQuery(e.target.value),
5577
+ onFocus: () => setSearchFocused(true),
5578
+ onBlur: () => setSearchFocused(false),
5579
+ placeholder: "Search by patient name or MRN",
5580
+ "aria-label": "Search conversations",
5581
+ style: styles16.searchInput
5582
+ }
5583
+ )
5584
+ ]
5585
+ }
5586
+ ),
5204
5587
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.list, children: [
5205
- isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles16.state, children: "Loading conversations..." }),
5206
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles16.errorState, children: error }),
5207
- !isLoading && !error && filtered.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles16.state, children: query ? "No matching conversations" : "No conversations yet" }),
5588
+ isLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.loading, children: [
5589
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 16 }),
5590
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Loading conversations\u2026" })
5591
+ ] }),
5592
+ error && !isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles16.errorState, children: error }),
5593
+ !isLoading && !error && filtered.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { filter, hasQuery: query.trim().length > 0 }),
5208
5594
  filtered.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
5209
5595
  ConversationListItem,
5210
5596
  {
@@ -5217,95 +5603,178 @@ function ConversationList({
5217
5603
  ] })
5218
5604
  ] });
5219
5605
  }
5606
+ function TabButton({
5607
+ label,
5608
+ count,
5609
+ isActive,
5610
+ onClick
5611
+ }) {
5612
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5613
+ "button",
5614
+ {
5615
+ type: "button",
5616
+ onClick,
5617
+ style: {
5618
+ ...styles16.tab,
5619
+ ...isActive ? styles16.tabActive : {}
5620
+ },
5621
+ children: [
5622
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
5623
+ /* @__PURE__ */ jsxRuntime.jsx(
5624
+ "span",
5625
+ {
5626
+ style: {
5627
+ ...styles16.tabCount,
5628
+ ...isActive ? styles16.tabCountActive : {}
5629
+ },
5630
+ children: count
5631
+ }
5632
+ )
5633
+ ]
5634
+ }
5635
+ );
5636
+ }
5637
+ function EmptyState({ filter, hasQuery }) {
5638
+ let title = "No conversations yet";
5639
+ let subtitle = "New chats will show up here as cases come in.";
5640
+ if (filter === "unread") {
5641
+ title = "No unread messages";
5642
+ subtitle = "You're all caught up.";
5643
+ } else if (hasQuery) {
5644
+ title = "No conversations match";
5645
+ subtitle = "Try a different search.";
5646
+ }
5647
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.empty, children: [
5648
+ /* @__PURE__ */ jsxRuntime.jsx(ChatIllustration, { size: 88 }),
5649
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles16.emptyTitle, children: title }),
5650
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles16.emptySubtitle, children: subtitle })
5651
+ ] });
5652
+ }
5220
5653
  var styles16 = {
5221
5654
  container: {
5222
5655
  display: "flex",
5223
5656
  flexDirection: "column",
5224
5657
  height: "100%",
5225
- backgroundColor: COLOR.white,
5226
- borderRight: `1px solid ${COLOR.neutral200}`
5658
+ backgroundColor: COLOR.white
5227
5659
  },
5228
- header: {
5229
- padding: SPACE.S3,
5660
+ // Tabs sit at the top of the panel now (header section removed).
5661
+ // A bit more top padding so they don't crowd the top edge.
5662
+ tabs: {
5663
+ display: "flex",
5664
+ gap: SPACE.S2,
5665
+ padding: `${SPACE.S5} ${SPACE.S5} ${SPACE.S2}`,
5230
5666
  borderBottom: `1px solid ${COLOR.neutral200}`,
5231
- backgroundColor: COLOR.neutral50,
5232
5667
  flexShrink: 0
5233
5668
  },
5234
- titleRow: {
5235
- display: "flex",
5669
+ tab: {
5670
+ display: "inline-flex",
5236
5671
  alignItems: "center",
5237
5672
  gap: SPACE.S2,
5238
- marginBottom: SPACE.S2
5239
- },
5240
- title: {
5241
- margin: 0,
5242
- fontSize: FONT_SIZE.md,
5673
+ padding: `${SPACE.S2} ${SPACE.S4}`,
5674
+ backgroundColor: "transparent",
5675
+ border: "none",
5676
+ borderRadius: RADIUS.lg,
5677
+ cursor: "pointer",
5678
+ color: COLOR.neutral500,
5679
+ fontFamily: "inherit",
5680
+ fontSize: FONT_SIZE.sm,
5243
5681
  fontWeight: FONT_WEIGHT.semibold,
5244
- color: COLOR.neutral900
5682
+ transition: "background-color 120ms ease, color 120ms ease"
5245
5683
  },
5246
- totalBadge: {
5247
- minWidth: "20px",
5248
- height: "20px",
5249
- padding: `0 ${SPACE.S2}`,
5684
+ tabActive: {
5685
+ backgroundColor: COLOR.primaryBg,
5686
+ color: COLOR.primary
5687
+ },
5688
+ tabCount: {
5250
5689
  fontSize: FONT_SIZE.xs,
5251
- fontWeight: FONT_WEIGHT.semibold,
5252
- color: COLOR.white,
5253
- backgroundColor: COLOR.primary,
5690
+ fontVariantNumeric: "tabular-nums",
5691
+ padding: "1px 8px",
5254
5692
  borderRadius: RADIUS.pill,
5693
+ backgroundColor: COLOR.neutral200,
5694
+ color: COLOR.neutral500,
5695
+ fontWeight: FONT_WEIGHT.semibold
5696
+ },
5697
+ tabCountActive: {
5698
+ backgroundColor: COLOR.primary,
5699
+ color: COLOR.white
5700
+ },
5701
+ search: {
5255
5702
  display: "flex",
5256
5703
  alignItems: "center",
5257
- justifyContent: "center"
5258
- },
5259
- searchRow: {
5260
- marginBottom: SPACE.S2
5704
+ gap: SPACE.S2,
5705
+ margin: `14px ${SPACE.S5} ${SPACE.S3}`,
5706
+ padding: `${SPACE.S3} 14px`,
5707
+ backgroundColor: COLOR.neutral100,
5708
+ border: `1.5px solid ${COLOR.neutral200}`,
5709
+ borderRadius: "14px",
5710
+ transition: "background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease"
5261
5711
  },
5262
- searchInput: {
5263
- width: "100%",
5264
- padding: `${SPACE.S2} ${SPACE.S3}`,
5265
- fontSize: FONT_SIZE.sm,
5266
- border: `1px solid ${COLOR.neutral200}`,
5267
- borderRadius: RADIUS.md,
5268
- outline: "none",
5269
- fontFamily: "inherit"
5712
+ searchFocused: {
5713
+ backgroundColor: COLOR.white,
5714
+ borderColor: COLOR.primary,
5715
+ boxShadow: `0 0 0 3px ${COLOR.primaryBg}`
5270
5716
  },
5271
- filterRow: {
5717
+ searchIcon: {
5272
5718
  display: "flex",
5273
- gap: SPACE.S1
5719
+ alignItems: "center",
5720
+ justifyContent: "center",
5721
+ flexShrink: 0
5274
5722
  },
5275
- filterButton: {
5723
+ searchInput: {
5276
5724
  flex: 1,
5277
- padding: `${SPACE.S1} ${SPACE.S2}`,
5278
- fontSize: FONT_SIZE.xs,
5279
- color: COLOR.neutral500,
5280
- backgroundColor: COLOR.white,
5281
- border: `1px solid ${COLOR.neutral200}`,
5282
- borderRadius: RADIUS.md,
5283
- cursor: "pointer"
5284
- },
5285
- filterButtonActive: {
5286
- color: COLOR.white,
5287
- backgroundColor: COLOR.primary,
5288
- borderColor: COLOR.primary,
5289
- fontWeight: FONT_WEIGHT.medium
5725
+ minWidth: 0,
5726
+ border: "none",
5727
+ background: "transparent",
5728
+ outline: "none",
5729
+ fontFamily: "inherit",
5730
+ fontSize: FONT_SIZE.md,
5731
+ color: COLOR.neutral900
5290
5732
  },
5291
5733
  list: {
5292
5734
  flex: 1,
5293
- overflowY: "auto"
5735
+ overflowY: "auto",
5736
+ padding: "4px 0 12px"
5294
5737
  },
5295
- state: {
5296
- padding: `${SPACE.S6} ${SPACE.S4}`,
5738
+ loading: {
5739
+ display: "flex",
5740
+ alignItems: "center",
5741
+ justifyContent: "center",
5742
+ gap: SPACE.S2,
5743
+ padding: `44px ${SPACE.S5}`,
5297
5744
  fontSize: FONT_SIZE.sm,
5298
- color: COLOR.neutral500,
5299
- textAlign: "center"
5745
+ color: COLOR.neutral500
5300
5746
  },
5301
5747
  errorState: {
5302
5748
  padding: SPACE.S4,
5749
+ margin: SPACE.S3,
5303
5750
  fontSize: FONT_SIZE.sm,
5304
5751
  color: COLOR.danger,
5305
5752
  backgroundColor: COLOR.dangerBg,
5306
- margin: SPACE.S3,
5753
+ border: `1px solid ${COLOR.dangerBorder}`,
5307
5754
  borderRadius: RADIUS.md,
5308
5755
  textAlign: "center"
5756
+ },
5757
+ empty: {
5758
+ display: "flex",
5759
+ flexDirection: "column",
5760
+ alignItems: "center",
5761
+ justifyContent: "center",
5762
+ padding: "44px 24px",
5763
+ textAlign: "center",
5764
+ color: COLOR.neutral500
5765
+ },
5766
+ emptyTitle: {
5767
+ margin: "12px 0 4px",
5768
+ fontSize: FONT_SIZE.md,
5769
+ fontWeight: FONT_WEIGHT.semibold,
5770
+ color: COLOR.neutral700
5771
+ },
5772
+ emptySubtitle: {
5773
+ margin: 0,
5774
+ fontSize: FONT_SIZE.sm,
5775
+ color: COLOR.neutral500,
5776
+ maxWidth: "280px",
5777
+ lineHeight: LINE_HEIGHT.normal
5309
5778
  }
5310
5779
  };
5311
5780
  ensureGlobalStyles();
@@ -5313,7 +5782,6 @@ var COMPACT_BREAKPOINT = 720;
5313
5782
  function CollabInbox({
5314
5783
  initialConversationId,
5315
5784
  onSelectConversation,
5316
- title,
5317
5785
  className,
5318
5786
  style
5319
5787
  }) {
@@ -5350,8 +5818,7 @@ function CollabInbox({
5350
5818
  selectedId,
5351
5819
  isLoading,
5352
5820
  error,
5353
- onSelect: handleSelect,
5354
- title
5821
+ onSelect: handleSelect
5355
5822
  }
5356
5823
  ) }),
5357
5824
  showDetail && /* @__PURE__ */ jsxRuntime.jsx("div", { style: isCompact ? styles17.mainCompact : styles17.main, children: selected ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -5364,7 +5831,7 @@ function CollabInbox({
5364
5831
  onBack: isCompact ? () => setSelectedId(null) : void 0
5365
5832
  },
5366
5833
  selected.id
5367
- ) : /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { hasAny: conversations.length > 0 }) })
5834
+ ) : /* @__PURE__ */ jsxRuntime.jsx(EmptyState2, { hasAny: conversations.length > 0 }) })
5368
5835
  ]
5369
5836
  }
5370
5837
  );
@@ -5398,7 +5865,7 @@ function parseChannelName(name, orderId) {
5398
5865
  orderId
5399
5866
  };
5400
5867
  }
5401
- function EmptyState({ hasAny }) {
5868
+ function EmptyState2({ hasAny }) {
5402
5869
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles17.empty, children: [
5403
5870
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles17.emptyIcon, children: "\u{1F4AC}" }),
5404
5871
  /* @__PURE__ */ jsxRuntime.jsx("h3", { style: styles17.emptyTitle, children: hasAny ? "Select a conversation" : "No conversations yet" }),
@@ -5804,7 +6271,10 @@ exports.ReplyPreview = ReplyPreview;
5804
6271
  exports.ReplyQuoteBlock = ReplyQuoteBlock;
5805
6272
  exports.SUPPORTED_IMAGE_TYPES = SUPPORTED_IMAGE_TYPES;
5806
6273
  exports.SeenByIndicator = SeenByIndicator;
6274
+ exports.THEME_DEFAULTS = THEME_DEFAULTS;
6275
+ exports.THEME_VAR = THEME_VAR;
5807
6276
  exports.TYPING_DEBOUNCE_MS = TYPING_DEBOUNCE_MS;
6277
+ exports.applyThemeOverrides = applyThemeOverrides;
5808
6278
  exports.useAudioRecorder = useAudioRecorder;
5809
6279
  exports.useChannelSettings = useChannelSettings;
5810
6280
  exports.useCollab = useCollab;