@natoe/colab 0.1.13 → 0.1.16

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.d.mts CHANGED
@@ -15,6 +15,17 @@ declare const THEME_VAR: {
15
15
  readonly dangerBorder: "--natoe-colab-danger-border";
16
16
  readonly dangerFg: "--natoe-colab-danger-fg";
17
17
  readonly fontStack: "--natoe-colab-font-stack";
18
+ readonly white: "--natoe-colab-white";
19
+ readonly neutral50: "--natoe-colab-neutral-50";
20
+ readonly neutral100: "--natoe-colab-neutral-100";
21
+ readonly neutral200: "--natoe-colab-neutral-200";
22
+ readonly neutral300: "--natoe-colab-neutral-300";
23
+ readonly neutral400: "--natoe-colab-neutral-400";
24
+ readonly neutral500: "--natoe-colab-neutral-500";
25
+ readonly neutral600: "--natoe-colab-neutral-600";
26
+ readonly neutral700: "--natoe-colab-neutral-700";
27
+ readonly neutral800: "--natoe-colab-neutral-800";
28
+ readonly neutral900: "--natoe-colab-neutral-900";
18
29
  };
19
30
  /** Default values for the themeable tokens (used by both CSS emission and
20
31
  * the inline-style fallback inside `var(..., fallback)`). */
@@ -30,6 +41,17 @@ declare const THEME_DEFAULTS: {
30
41
  readonly dangerBorder: "#fecaca";
31
42
  readonly dangerFg: "#b91c1c";
32
43
  readonly fontStack: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif";
44
+ readonly white: "#ffffff";
45
+ readonly neutral50: "#f9fafb";
46
+ readonly neutral100: "#f3f4f6";
47
+ readonly neutral200: "#e5e7eb";
48
+ readonly neutral300: "#d1d5db";
49
+ readonly neutral400: "#9ca3af";
50
+ readonly neutral500: "#6b7280";
51
+ readonly neutral600: "#4b5563";
52
+ readonly neutral700: "#374151";
53
+ readonly neutral800: "#1f2937";
54
+ readonly neutral900: "#111827";
33
55
  };
34
56
  /** Public override shape — passed via CollabConfig.theme. Every key is
35
57
  * optional; unspecified keys fall back to {@link THEME_DEFAULTS}. */
@@ -241,6 +263,14 @@ interface CollabPanelProps {
241
263
  * relying on potentially-incomplete patientData.
242
264
  */
243
265
  onConversationChange?: (conversation: Conversation | null) => void;
266
+ /**
267
+ * Color mode for the entire panel. Default 'light'. 'dark' is meant for
268
+ * embedding inside the viewer's left panel (#000 background) — applies
269
+ * dark overrides for the themeable CSS variables on the panel root, so
270
+ * every internal surface (PatientHeader, MessageList, MessageBubble,
271
+ * MessageInput, PinnedMessagesBar, etc.) inherits them via cascade.
272
+ */
273
+ themeMode?: 'light' | 'dark';
244
274
  /** Custom class name for the outer container */
245
275
  className?: string;
246
276
  /** Custom inline styles for the outer container */
@@ -250,7 +280,7 @@ interface CollabPanelProps {
250
280
  * Full collaboration panel: patient header + pinned bar + message thread + input.
251
281
  * Supports reply, pin/unpin, and (optionally) seen-by indicators.
252
282
  */
253
- declare function CollabPanel({ orderId, patientData, participantIds, showSeenBy, onBack, hidePatientName, onConversationChange, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
283
+ declare function CollabPanel({ orderId, patientData, participantIds, showSeenBy, onBack, hidePatientName, onConversationChange, themeMode, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
254
284
 
255
285
  interface CollabPopupProps {
256
286
  /** Order ID for this conversation */
@@ -290,6 +320,7 @@ interface CollabPopupProps {
290
320
  */
291
321
  declare function CollabPopup({ orderId, patientData, participantIds, isOpen, onClose, onBack, onMinimize, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
292
322
 
323
+ type ColabInlineMode = 'light' | 'dark';
293
324
  interface CollabInlineProps {
294
325
  orderId: string;
295
326
  patientData: PatientData;
@@ -297,24 +328,32 @@ interface CollabInlineProps {
297
328
  participantIds?: string[];
298
329
  /** Called when user clicks the expand button — host opens the floating popup */
299
330
  onExpand?: () => void;
300
- /** Max inline messages to show (default 5) */
331
+ /** Max inline messages to show (default 1 — just the latest) */
301
332
  messageLimit?: number;
302
333
  /** Placeholder text for the input */
303
334
  placeholder?: string;
335
+ /**
336
+ * Color mode for the inline surface. Defaults to 'light'. 'dark' is meant
337
+ * for embedding inside the viewer's left panel (black background) — only
338
+ * neutral/structural colors switch; brand/role/semantic colors stay the
339
+ * same in both modes.
340
+ */
341
+ mode?: ColabInlineMode;
304
342
  className?: string;
305
343
  style?: React.CSSProperties;
306
344
  }
307
345
  /**
308
- * Inline chat for table rows.
346
+ * Inline chat for table rows or side panels.
309
347
  *
310
348
  * Two states:
311
349
  * - No conversation yet: just an input bar (no socket, no preview)
312
- * - Conversation exists: last N messages + input + expand button
350
+ * - Conversation exists: latest message (or last N if messageLimit > 1)
351
+ * + input + expand button
313
352
  *
314
353
  * Supports text and voice messages. File attachments and full chat features
315
354
  * stay in the floating popup / inbox.
316
355
  */
317
- declare function CollabInline({ orderId, patientData, participantIds, onExpand, messageLimit, placeholder, className, style, }: CollabInlineProps): react_jsx_runtime.JSX.Element;
356
+ declare function CollabInline({ orderId, patientData, participantIds, onExpand, messageLimit, placeholder, mode, className, style, }: CollabInlineProps): react_jsx_runtime.JSX.Element;
318
357
 
319
358
  interface CollabInboxProps {
320
359
  /** Optional: preselect a specific conversation (by conversation ID) */
@@ -341,6 +380,20 @@ type PresenceCallback = (presences: Record<string, {
341
380
  userId: string;
342
381
  isOnline: boolean;
343
382
  }>) => void;
383
+ /**
384
+ * Server-pushed unread counts. The backend broadcasts BOTH keyings in
385
+ * one payload so consumers can render badges at either granularity (FAB
386
+ * + inbox row use `conversation`; table-row chat icons use `order`) off
387
+ * a single socket event. Legacy backends that send a flat
388
+ * `Record<string, number>` are normalised at the socket boundary into
389
+ * `{ conversation: <flat>, order: {} }` for forward compatibility.
390
+ */
391
+ interface UnreadCounts {
392
+ /** keyed by conversation id */
393
+ conversation: Record<string, number>;
394
+ /** keyed by order id — used for table-row chat-icon badges */
395
+ order: Record<string, number>;
396
+ }
344
397
  interface ConversationCallbacks {
345
398
  onMessage?: MessageCallback;
346
399
  onTyping?: TypingCallback;
@@ -358,12 +411,31 @@ interface ConversationCallbacks {
358
411
  messageId: string;
359
412
  }) => void;
360
413
  }
414
+ /**
415
+ * Returned by `joinConversation()`. Each caller gets its own handle and
416
+ * its own bound listeners — calling `release()` unbinds only THIS
417
+ * subscriber's listeners. The channel only actually leaves the server
418
+ * when the last subscriber releases.
419
+ */
420
+ interface ChannelSubscription {
421
+ /** Underlying Phoenix channel — callers that need to push/observe directly. */
422
+ channel: Channel;
423
+ /** Drop this subscriber. Idempotent — safe to call twice. */
424
+ release: () => void;
425
+ }
361
426
  /**
362
427
  * Manages Phoenix WebSocket connection and channel subscriptions.
363
428
  * One instance per authenticated user session.
364
429
  */
365
430
  declare class CollabSocket {
366
431
  private socket;
432
+ /**
433
+ * Conversation channels keyed by id. Each entry is reference-counted so
434
+ * multiple surfaces (e.g. inline chat + expanded panel mounted at once
435
+ * for the same conversation) can coexist without one's `leaveConversation`
436
+ * tearing the channel out from under the other. See `joinConversation`
437
+ * and the returned `ChannelSubscription.release`.
438
+ */
367
439
  private channels;
368
440
  private presences;
369
441
  private userChannel;
@@ -375,12 +447,34 @@ declare class CollabSocket {
375
447
  connect(config: CollabConfig): void;
376
448
  /** Subscribe to user-level events (unread counts, notifications) */
377
449
  private joinUserChannel;
378
- /** Register callback for unread count changes */
379
- onUnreadCountUpdate(callback: (counts: Record<string, number>) => void): void;
380
- /** Join a conversation channel and subscribe to events */
381
- joinConversation(conversationId: string, callbacks: ConversationCallbacks): Channel | null;
382
- /** Leave a conversation channel */
383
- leaveConversation(conversationId: string): void;
450
+ /** Register callback for unread count changes. The callback receives
451
+ * the normalised {@link UnreadCounts} shape regardless of which
452
+ * payload format the backend pushed. */
453
+ onUnreadCountUpdate(callback: (counts: UnreadCounts) => void): void;
454
+ /**
455
+ * Join a conversation channel and subscribe to events.
456
+ *
457
+ * Reference-counted: multiple callers can join the same conversation
458
+ * (e.g. inline preview + expanded panel mounted side-by-side). Each
459
+ * call binds its own listeners and gets back a `ChannelSubscription`.
460
+ * The underlying channel only `.leave()`s the server when the LAST
461
+ * subscriber calls `release()`.
462
+ */
463
+ joinConversation(conversationId: string, callbacks: ConversationCallbacks): ChannelSubscription | null;
464
+ /**
465
+ * @deprecated Use the `release()` method returned by `joinConversation()`.
466
+ * Kept as a no-op so older callers don't throw — but it cannot identify
467
+ * which subscriber should leave, so it silently does nothing. Any code
468
+ * still calling this will leak listeners and prevent the channel from
469
+ * ever being torn down. Migrate to the subscription handle.
470
+ */
471
+ leaveConversation(_conversationId: string): void;
472
+ /**
473
+ * Look up the underlying Phoenix Channel for a conversation, if any
474
+ * subscriber is still holding it. All send/push paths go through this
475
+ * helper so the refcounted entry shape is contained to joinConversation.
476
+ */
477
+ private getChannel;
384
478
  /** Send a message to a conversation.
385
479
  *
386
480
  * Phoenix buffers pushes fired while a channel is still joining and
@@ -434,6 +528,13 @@ interface CollabContextValue {
434
528
  * mounting their own useConversation.
435
529
  */
436
530
  unreadCounts: Record<string, number>;
531
+ /**
532
+ * Same unread counts, but keyed by **order ID** instead of
533
+ * conversation ID. Useful for table-row chat-icon badges where the
534
+ * row only knows its order_id, not the conversation_id. Same socket
535
+ * push updates both maps in lock-step.
536
+ */
537
+ unreadCountsByOrder: Record<string, number>;
437
538
  /** Batched preview lookup — coalesces calls within a microtask */
438
539
  requestPreview: (orderId: string) => Promise<ConversationPreview | null>;
439
540
  /** Invalidate a cached preview (e.g. when a new message arrives) */
@@ -716,8 +817,13 @@ interface ConversationListProps {
716
817
  /**
717
818
  * Scrollable inbox of conversations. Unread/All tabs (with count pills)
718
819
  * sit at the top, followed by a search field with a focus-within ring.
719
- * On first data load with any unread items the inbox auto-defaults to
720
- * the Unread tab so triage starts on what matters. Empty/loading/error
820
+ * The inbox lands on Unread by default whenever there are any unread
821
+ * conversations the initial choice reads CollabProvider's socket-
822
+ * pushed `totalUnread`, which is already populated by the time the
823
+ * FAB is clicked (it drives the badge), so there's no "All flash"
824
+ * during the conversation-list fetch. A fallback effect handles the
825
+ * edge case where the conversation list loads with unread items but
826
+ * `totalUnread` was still zero at mount time. Empty/loading/error
721
827
  * states are illustrated and descriptive rather than terse one-liners.
722
828
  */
723
829
  declare function ConversationList({ conversations, selectedId, isLoading, error, onSelect, className, }: ConversationListProps): react_jsx_runtime.JSX.Element;
@@ -774,7 +880,7 @@ interface PatientHeaderProps {
774
880
  * experience and there's no outer title, so we still show the name +
775
881
  * back button stacked above the case card.
776
882
  */
777
- declare function PatientHeader({ patientData, onOpenDicom, onOpenSettings, onBack, hideName, displayName, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
883
+ declare function PatientHeader({ patientData, participants, onOpenDicom, onOpenSettings, onBack, hideName, displayName, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
778
884
 
779
885
  interface MessageListHandle {
780
886
  /** Scroll to a specific message by ID (used by pin jump-to) */
package/dist/index.d.ts CHANGED
@@ -15,6 +15,17 @@ declare const THEME_VAR: {
15
15
  readonly dangerBorder: "--natoe-colab-danger-border";
16
16
  readonly dangerFg: "--natoe-colab-danger-fg";
17
17
  readonly fontStack: "--natoe-colab-font-stack";
18
+ readonly white: "--natoe-colab-white";
19
+ readonly neutral50: "--natoe-colab-neutral-50";
20
+ readonly neutral100: "--natoe-colab-neutral-100";
21
+ readonly neutral200: "--natoe-colab-neutral-200";
22
+ readonly neutral300: "--natoe-colab-neutral-300";
23
+ readonly neutral400: "--natoe-colab-neutral-400";
24
+ readonly neutral500: "--natoe-colab-neutral-500";
25
+ readonly neutral600: "--natoe-colab-neutral-600";
26
+ readonly neutral700: "--natoe-colab-neutral-700";
27
+ readonly neutral800: "--natoe-colab-neutral-800";
28
+ readonly neutral900: "--natoe-colab-neutral-900";
18
29
  };
19
30
  /** Default values for the themeable tokens (used by both CSS emission and
20
31
  * the inline-style fallback inside `var(..., fallback)`). */
@@ -30,6 +41,17 @@ declare const THEME_DEFAULTS: {
30
41
  readonly dangerBorder: "#fecaca";
31
42
  readonly dangerFg: "#b91c1c";
32
43
  readonly fontStack: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif";
44
+ readonly white: "#ffffff";
45
+ readonly neutral50: "#f9fafb";
46
+ readonly neutral100: "#f3f4f6";
47
+ readonly neutral200: "#e5e7eb";
48
+ readonly neutral300: "#d1d5db";
49
+ readonly neutral400: "#9ca3af";
50
+ readonly neutral500: "#6b7280";
51
+ readonly neutral600: "#4b5563";
52
+ readonly neutral700: "#374151";
53
+ readonly neutral800: "#1f2937";
54
+ readonly neutral900: "#111827";
33
55
  };
34
56
  /** Public override shape — passed via CollabConfig.theme. Every key is
35
57
  * optional; unspecified keys fall back to {@link THEME_DEFAULTS}. */
@@ -241,6 +263,14 @@ interface CollabPanelProps {
241
263
  * relying on potentially-incomplete patientData.
242
264
  */
243
265
  onConversationChange?: (conversation: Conversation | null) => void;
266
+ /**
267
+ * Color mode for the entire panel. Default 'light'. 'dark' is meant for
268
+ * embedding inside the viewer's left panel (#000 background) — applies
269
+ * dark overrides for the themeable CSS variables on the panel root, so
270
+ * every internal surface (PatientHeader, MessageList, MessageBubble,
271
+ * MessageInput, PinnedMessagesBar, etc.) inherits them via cascade.
272
+ */
273
+ themeMode?: 'light' | 'dark';
244
274
  /** Custom class name for the outer container */
245
275
  className?: string;
246
276
  /** Custom inline styles for the outer container */
@@ -250,7 +280,7 @@ interface CollabPanelProps {
250
280
  * Full collaboration panel: patient header + pinned bar + message thread + input.
251
281
  * Supports reply, pin/unpin, and (optionally) seen-by indicators.
252
282
  */
253
- declare function CollabPanel({ orderId, patientData, participantIds, showSeenBy, onBack, hidePatientName, onConversationChange, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
283
+ declare function CollabPanel({ orderId, patientData, participantIds, showSeenBy, onBack, hidePatientName, onConversationChange, themeMode, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
254
284
 
255
285
  interface CollabPopupProps {
256
286
  /** Order ID for this conversation */
@@ -290,6 +320,7 @@ interface CollabPopupProps {
290
320
  */
291
321
  declare function CollabPopup({ orderId, patientData, participantIds, isOpen, onClose, onBack, onMinimize, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
292
322
 
323
+ type ColabInlineMode = 'light' | 'dark';
293
324
  interface CollabInlineProps {
294
325
  orderId: string;
295
326
  patientData: PatientData;
@@ -297,24 +328,32 @@ interface CollabInlineProps {
297
328
  participantIds?: string[];
298
329
  /** Called when user clicks the expand button — host opens the floating popup */
299
330
  onExpand?: () => void;
300
- /** Max inline messages to show (default 5) */
331
+ /** Max inline messages to show (default 1 — just the latest) */
301
332
  messageLimit?: number;
302
333
  /** Placeholder text for the input */
303
334
  placeholder?: string;
335
+ /**
336
+ * Color mode for the inline surface. Defaults to 'light'. 'dark' is meant
337
+ * for embedding inside the viewer's left panel (black background) — only
338
+ * neutral/structural colors switch; brand/role/semantic colors stay the
339
+ * same in both modes.
340
+ */
341
+ mode?: ColabInlineMode;
304
342
  className?: string;
305
343
  style?: React.CSSProperties;
306
344
  }
307
345
  /**
308
- * Inline chat for table rows.
346
+ * Inline chat for table rows or side panels.
309
347
  *
310
348
  * Two states:
311
349
  * - No conversation yet: just an input bar (no socket, no preview)
312
- * - Conversation exists: last N messages + input + expand button
350
+ * - Conversation exists: latest message (or last N if messageLimit > 1)
351
+ * + input + expand button
313
352
  *
314
353
  * Supports text and voice messages. File attachments and full chat features
315
354
  * stay in the floating popup / inbox.
316
355
  */
317
- declare function CollabInline({ orderId, patientData, participantIds, onExpand, messageLimit, placeholder, className, style, }: CollabInlineProps): react_jsx_runtime.JSX.Element;
356
+ declare function CollabInline({ orderId, patientData, participantIds, onExpand, messageLimit, placeholder, mode, className, style, }: CollabInlineProps): react_jsx_runtime.JSX.Element;
318
357
 
319
358
  interface CollabInboxProps {
320
359
  /** Optional: preselect a specific conversation (by conversation ID) */
@@ -341,6 +380,20 @@ type PresenceCallback = (presences: Record<string, {
341
380
  userId: string;
342
381
  isOnline: boolean;
343
382
  }>) => void;
383
+ /**
384
+ * Server-pushed unread counts. The backend broadcasts BOTH keyings in
385
+ * one payload so consumers can render badges at either granularity (FAB
386
+ * + inbox row use `conversation`; table-row chat icons use `order`) off
387
+ * a single socket event. Legacy backends that send a flat
388
+ * `Record<string, number>` are normalised at the socket boundary into
389
+ * `{ conversation: <flat>, order: {} }` for forward compatibility.
390
+ */
391
+ interface UnreadCounts {
392
+ /** keyed by conversation id */
393
+ conversation: Record<string, number>;
394
+ /** keyed by order id — used for table-row chat-icon badges */
395
+ order: Record<string, number>;
396
+ }
344
397
  interface ConversationCallbacks {
345
398
  onMessage?: MessageCallback;
346
399
  onTyping?: TypingCallback;
@@ -358,12 +411,31 @@ interface ConversationCallbacks {
358
411
  messageId: string;
359
412
  }) => void;
360
413
  }
414
+ /**
415
+ * Returned by `joinConversation()`. Each caller gets its own handle and
416
+ * its own bound listeners — calling `release()` unbinds only THIS
417
+ * subscriber's listeners. The channel only actually leaves the server
418
+ * when the last subscriber releases.
419
+ */
420
+ interface ChannelSubscription {
421
+ /** Underlying Phoenix channel — callers that need to push/observe directly. */
422
+ channel: Channel;
423
+ /** Drop this subscriber. Idempotent — safe to call twice. */
424
+ release: () => void;
425
+ }
361
426
  /**
362
427
  * Manages Phoenix WebSocket connection and channel subscriptions.
363
428
  * One instance per authenticated user session.
364
429
  */
365
430
  declare class CollabSocket {
366
431
  private socket;
432
+ /**
433
+ * Conversation channels keyed by id. Each entry is reference-counted so
434
+ * multiple surfaces (e.g. inline chat + expanded panel mounted at once
435
+ * for the same conversation) can coexist without one's `leaveConversation`
436
+ * tearing the channel out from under the other. See `joinConversation`
437
+ * and the returned `ChannelSubscription.release`.
438
+ */
367
439
  private channels;
368
440
  private presences;
369
441
  private userChannel;
@@ -375,12 +447,34 @@ declare class CollabSocket {
375
447
  connect(config: CollabConfig): void;
376
448
  /** Subscribe to user-level events (unread counts, notifications) */
377
449
  private joinUserChannel;
378
- /** Register callback for unread count changes */
379
- onUnreadCountUpdate(callback: (counts: Record<string, number>) => void): void;
380
- /** Join a conversation channel and subscribe to events */
381
- joinConversation(conversationId: string, callbacks: ConversationCallbacks): Channel | null;
382
- /** Leave a conversation channel */
383
- leaveConversation(conversationId: string): void;
450
+ /** Register callback for unread count changes. The callback receives
451
+ * the normalised {@link UnreadCounts} shape regardless of which
452
+ * payload format the backend pushed. */
453
+ onUnreadCountUpdate(callback: (counts: UnreadCounts) => void): void;
454
+ /**
455
+ * Join a conversation channel and subscribe to events.
456
+ *
457
+ * Reference-counted: multiple callers can join the same conversation
458
+ * (e.g. inline preview + expanded panel mounted side-by-side). Each
459
+ * call binds its own listeners and gets back a `ChannelSubscription`.
460
+ * The underlying channel only `.leave()`s the server when the LAST
461
+ * subscriber calls `release()`.
462
+ */
463
+ joinConversation(conversationId: string, callbacks: ConversationCallbacks): ChannelSubscription | null;
464
+ /**
465
+ * @deprecated Use the `release()` method returned by `joinConversation()`.
466
+ * Kept as a no-op so older callers don't throw — but it cannot identify
467
+ * which subscriber should leave, so it silently does nothing. Any code
468
+ * still calling this will leak listeners and prevent the channel from
469
+ * ever being torn down. Migrate to the subscription handle.
470
+ */
471
+ leaveConversation(_conversationId: string): void;
472
+ /**
473
+ * Look up the underlying Phoenix Channel for a conversation, if any
474
+ * subscriber is still holding it. All send/push paths go through this
475
+ * helper so the refcounted entry shape is contained to joinConversation.
476
+ */
477
+ private getChannel;
384
478
  /** Send a message to a conversation.
385
479
  *
386
480
  * Phoenix buffers pushes fired while a channel is still joining and
@@ -434,6 +528,13 @@ interface CollabContextValue {
434
528
  * mounting their own useConversation.
435
529
  */
436
530
  unreadCounts: Record<string, number>;
531
+ /**
532
+ * Same unread counts, but keyed by **order ID** instead of
533
+ * conversation ID. Useful for table-row chat-icon badges where the
534
+ * row only knows its order_id, not the conversation_id. Same socket
535
+ * push updates both maps in lock-step.
536
+ */
537
+ unreadCountsByOrder: Record<string, number>;
437
538
  /** Batched preview lookup — coalesces calls within a microtask */
438
539
  requestPreview: (orderId: string) => Promise<ConversationPreview | null>;
439
540
  /** Invalidate a cached preview (e.g. when a new message arrives) */
@@ -716,8 +817,13 @@ interface ConversationListProps {
716
817
  /**
717
818
  * Scrollable inbox of conversations. Unread/All tabs (with count pills)
718
819
  * sit at the top, followed by a search field with a focus-within ring.
719
- * On first data load with any unread items the inbox auto-defaults to
720
- * the Unread tab so triage starts on what matters. Empty/loading/error
820
+ * The inbox lands on Unread by default whenever there are any unread
821
+ * conversations the initial choice reads CollabProvider's socket-
822
+ * pushed `totalUnread`, which is already populated by the time the
823
+ * FAB is clicked (it drives the badge), so there's no "All flash"
824
+ * during the conversation-list fetch. A fallback effect handles the
825
+ * edge case where the conversation list loads with unread items but
826
+ * `totalUnread` was still zero at mount time. Empty/loading/error
721
827
  * states are illustrated and descriptive rather than terse one-liners.
722
828
  */
723
829
  declare function ConversationList({ conversations, selectedId, isLoading, error, onSelect, className, }: ConversationListProps): react_jsx_runtime.JSX.Element;
@@ -774,7 +880,7 @@ interface PatientHeaderProps {
774
880
  * experience and there's no outer title, so we still show the name +
775
881
  * back button stacked above the case card.
776
882
  */
777
- declare function PatientHeader({ patientData, onOpenDicom, onOpenSettings, onBack, hideName, displayName, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
883
+ declare function PatientHeader({ patientData, participants, onOpenDicom, onOpenSettings, onBack, hideName, displayName, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
778
884
 
779
885
  interface MessageListHandle {
780
886
  /** Scroll to a specific message by ID (used by pin jump-to) */