@natoe/colab 0.1.27 → 0.1.30

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
@@ -118,17 +118,51 @@ interface PatientData {
118
118
  labName?: string;
119
119
  bodyParts?: string[];
120
120
  }
121
+ /**
122
+ * What a thread is about — and, crucially, what it is SCOPED to.
123
+ *
124
+ * - `case` — the discussion about one clinical order. Scoped to an order,
125
+ * so `orderId` is always set. Lab, radiologist, physician, admin.
126
+ * - `help_lab` — an imaging centre's help channel with Natoe support. Scoped to
127
+ * the LAB, not to any case: it has NO `orderId`, it outlives
128
+ * every order, and there is exactly one per imaging centre.
129
+ *
130
+ * `help_lab` was briefly modelled as per-case (one help thread per order). It is
131
+ * now per-lab, so support gets one continuous thread with each imaging centre
132
+ * rather than N short-lived ones scattered across cases. A case can still be
133
+ * referenced from inside the channel; it just no longer scopes it.
134
+ *
135
+ * Isolation — who can see a help channel — is enforced by the backend's
136
+ * participant seeding, never by anything in this package. Treat this as a label
137
+ * for rendering and for addressing the right thread, not as a security boundary.
138
+ *
139
+ * Optional everywhere, defaulting to `case`, so callers written before help
140
+ * channels existed keep their exact previous behaviour.
141
+ */
142
+ type ConversationKind = 'case' | 'help_lab';
143
+ declare function isHelpKind(kind?: ConversationKind | string): boolean;
144
+ /**
145
+ * True when a thread is scoped to an order and therefore has patient context to
146
+ * render. Prefer this over testing `orderId` directly: a help channel has no
147
+ * order, so every patient-shaped affordance must be skipped for it.
148
+ */
149
+ declare function isCaseKind(kind?: ConversationKind | string): boolean;
121
150
  interface Conversation {
122
151
  id: string;
123
- orderId: string;
152
+ /** Absent on a help channel — that thread belongs to a lab, not an order. */
153
+ orderId?: string;
154
+ /** Set only on a help channel: the imaging centre that owns it. */
155
+ labUserId?: string;
124
156
  name: string;
157
+ kind?: ConversationKind;
125
158
  picture?: string;
126
159
  participants: Participant[];
127
160
  lastMessage?: Message;
128
161
  unreadCount: number;
129
162
  createdAt: string;
130
163
  updatedAt: string;
131
- /** Denormalized patient data snapshot — lets UI render without extra fetch */
164
+ /** Denormalized patient data snapshot — lets UI render without extra fetch.
165
+ * Always absent on a help channel: there is no patient. */
132
166
  patientSnapshot?: PatientData;
133
167
  /** Currently pinned messages (max 3) */
134
168
  pinnedMessages?: Message[];
@@ -139,8 +173,13 @@ interface Conversation {
139
173
  */
140
174
  interface ConversationListItem$1 {
141
175
  id: string;
142
- orderId: string;
176
+ /** Absent on a help channel — see `Conversation.orderId`. */
177
+ orderId?: string;
178
+ labUserId?: string;
143
179
  name: string;
180
+ /** Lets the inbox render the lab's help channel distinctly from its case
181
+ * threads — they appear as separate rows. */
182
+ kind?: ConversationKind;
144
183
  picture?: string;
145
184
  unreadCount: number;
146
185
  lastMessage?: Message;
@@ -154,6 +193,7 @@ interface ConversationListItem$1 {
154
193
  interface ConversationPreview {
155
194
  conversationId: string;
156
195
  orderId: string;
196
+ kind?: ConversationKind;
157
197
  messageCount: number;
158
198
  unreadCount: number;
159
199
  /** Last N messages (typically 5) for inline display */
@@ -163,7 +203,9 @@ interface ConversationPreview {
163
203
  }
164
204
  /** Batch preview response shape from the backend */
165
205
  interface PreviewBatchResponse {
166
- /** Key is orderId, value is preview or null if no conversation exists */
206
+ /** Key is orderId, value is preview or null if no conversation exists.
207
+ * Scoped to a single `kind` per request — the backend filters server-side,
208
+ * so this map never mixes a case thread and a help thread for one order. */
167
209
  previews: Record<string, ConversationPreview | null>;
168
210
  }
169
211
  /** Response from createConversationWithMessage (first-message flow) */
@@ -261,12 +303,19 @@ interface InviteUserPayload {
261
303
  }
262
304
 
263
305
  interface CollabPanelProps {
264
- /** Order ID one conversation per study */
265
- orderId: string;
266
- /** Patient/study data displayed in the header */
267
- patientData: PatientData;
306
+ /** Order this thread belongs to. Required for a `case` thread; omitted for a
307
+ * help channel, which belongs to a lab rather than an order. */
308
+ orderId?: string;
309
+ /** A thread the caller already resolved — how a help channel is opened.
310
+ * Skips the order-keyed preview lookup entirely. */
311
+ conversation?: Conversation | null;
312
+ /** Patient/study data displayed in the header. Absent on a help channel:
313
+ * there is no patient, and the header renders its support variant instead. */
314
+ patientData?: PatientData;
268
315
  /** Participant user IDs — only used when creating a brand-new conversation */
269
316
  participantIds?: string[];
317
+ /** Which thread on the case. Defaults to the shared `case` thread. */
318
+ kind?: ConversationKind;
270
319
  /** Show "seen by" indicator under own messages (default true) */
271
320
  showSeenBy?: boolean;
272
321
  /**
@@ -317,7 +366,7 @@ interface CollabPanelProps {
317
366
  * Full collaboration panel: patient header + pinned bar + message thread + input.
318
367
  * Supports reply, pin/unpin, and (optionally) seen-by indicators.
319
368
  */
320
- declare function CollabPanel({ orderId, patientData, participantIds, showSeenBy, onBack, hidePatientName, hideOpenCase, onConversationChange, themeMode, showSettings: controlledShowSettings, onSettingsChange, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
369
+ declare function CollabPanel({ orderId, conversation: providedConversation, patientData, participantIds, kind, showSeenBy, onBack, hidePatientName, hideOpenCase, onConversationChange, themeMode, showSettings: controlledShowSettings, onSettingsChange, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
321
370
 
322
371
  interface CollabPopupProps {
323
372
  /** Order ID for this conversation */
@@ -326,6 +375,8 @@ interface CollabPopupProps {
326
375
  patientData: PatientData;
327
376
  /** Participant user IDs — only used when creating a brand-new conversation */
328
377
  participantIds?: string[];
378
+ /** Which thread on the case. Defaults to the shared `case` thread. */
379
+ kind?: ConversationKind;
329
380
  /** Whether the popup is open */
330
381
  isOpen: boolean;
331
382
  /** Close the popup */
@@ -355,7 +406,7 @@ interface CollabPopupProps {
355
406
  * Draggable floating chat popup — drop-in replacement for DraggableChatPopup.
356
407
  * Renders a CollabPanel inside a movable, resizable container.
357
408
  */
358
- declare function CollabPopup({ orderId, patientData, participantIds, isOpen, onClose, onBack, onMinimize, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
409
+ declare function CollabPopup({ orderId, patientData, participantIds, kind, isOpen, onClose, onBack, onMinimize, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
359
410
 
360
411
  type ColabInlineMode = 'light' | 'dark';
361
412
  interface CollabInlineProps {
@@ -363,6 +414,8 @@ interface CollabInlineProps {
363
414
  patientData: PatientData;
364
415
  /** User IDs to invite when the first message creates the conversation */
365
416
  participantIds?: string[];
417
+ /** Which thread on the case. Defaults to the shared `case` thread. */
418
+ kind?: ConversationKind;
366
419
  /** Called when user clicks the expand button — host opens the floating popup */
367
420
  onExpand?: () => void;
368
421
  /** Max inline messages to show (default 1 — just the latest) */
@@ -390,7 +443,7 @@ interface CollabInlineProps {
390
443
  * Supports text and voice messages. File attachments and full chat features
391
444
  * stay in the floating popup / inbox.
392
445
  */
393
- declare function CollabInline({ orderId, patientData, participantIds, onExpand, messageLimit, placeholder, mode, className, style, }: CollabInlineProps): react_jsx_runtime.JSX.Element;
446
+ declare function CollabInline({ orderId, patientData, participantIds, kind, onExpand, messageLimit, placeholder, mode, className, style, }: CollabInlineProps): react_jsx_runtime.JSX.Element;
394
447
 
395
448
  interface CollabInboxProps {
396
449
  /** Optional: preselect a specific conversation (by conversation ID) */
@@ -552,6 +605,8 @@ interface FetchMessagesOptions {
552
605
  interface CreateConversationOptions {
553
606
  name: string;
554
607
  participantIds?: string[];
608
+ /** Which thread to create. Defaults to the shared case thread. */
609
+ kind?: ConversationKind;
555
610
  }
556
611
  interface CollabContextValue {
557
612
  socket: CollabSocket;
@@ -573,9 +628,9 @@ interface CollabContextValue {
573
628
  */
574
629
  unreadCountsByOrder: Record<string, number>;
575
630
  /** Batched preview lookup — coalesces calls within a microtask */
576
- requestPreview: (orderId: string) => Promise<ConversationPreview | null>;
631
+ requestPreview: (orderId: string, kind?: ConversationKind) => Promise<ConversationPreview | null>;
577
632
  /** Invalidate a cached preview (e.g. when a new message arrives) */
578
- invalidatePreview: (orderId: string) => void;
633
+ invalidatePreview: (orderId: string, kind?: ConversationKind) => void;
579
634
  /** Fetch paginated message history */
580
635
  fetchMessages: (conversationId: string, options?: FetchMessagesOptions) => Promise<Message[]>;
581
636
  /** Atomic create-or-send — creates conversation if missing, then stores the first message */
@@ -603,12 +658,38 @@ interface CollabProviderProps {
603
658
  declare function CollabProvider({ config, apiBaseUrl, children }: CollabProviderProps): react_jsx_runtime.JSX.Element | null;
604
659
 
605
660
  interface UseConversationOptions {
606
- orderId: string;
607
- patientData: PatientData;
661
+ /**
662
+ * The order this thread belongs to. Required for a `case` thread, which is
663
+ * created lazily on first message and resolved by an order-keyed preview.
664
+ * Omitted for a help channel — that belongs to a lab, not an order.
665
+ */
666
+ orderId?: string;
667
+ /**
668
+ * A thread the caller has ALREADY resolved, opened directly instead of being
669
+ * looked up by order.
670
+ *
671
+ * This is how a help channel opens. The backend's `GET /help_channel` does
672
+ * the get-or-create and hands back the conversation, so there is nothing to
673
+ * look up and nothing to lazily create — passing it here skips the preview
674
+ * request entirely. The preview endpoint is order-keyed and could not serve
675
+ * an order-less thread in any case.
676
+ */
677
+ conversation?: Conversation | null;
678
+ /** Patient/study context. Absent on a help channel — there is no patient. */
679
+ patientData?: PatientData;
608
680
  /** Participant user IDs used only when creating a brand-new conversation */
609
681
  participantIds?: string[];
610
682
  /** Load full history on mount (default true) */
611
683
  loadHistory?: boolean;
684
+ /**
685
+ * What this thread is. Defaults to the shared `case` thread, so every
686
+ * existing caller is unaffected.
687
+ *
688
+ * For `help_lab` the backend seats only the imaging centre plus Natoe
689
+ * support, and `participantIds` is ignored server-side — a help channel must
690
+ * never be widened by the client.
691
+ */
692
+ kind?: ConversationKind;
612
693
  }
613
694
  interface UseConversationReturn {
614
695
  conversation: Conversation | null;
@@ -643,7 +724,7 @@ interface UseConversationReturn {
643
724
  * when the user sends the first message. If a conversation already exists for
644
725
  * the given orderId, it's loaded and joined automatically.
645
726
  */
646
- declare function useConversation({ orderId, patientData, participantIds, loadHistory, }: UseConversationOptions): UseConversationReturn;
727
+ declare function useConversation({ orderId, conversation: providedConversation, patientData, participantIds, loadHistory, kind, }: UseConversationOptions): UseConversationReturn;
647
728
 
648
729
  interface UseConversationListOptions {
649
730
  /**
@@ -677,6 +758,8 @@ interface UseInlineCollabOptions {
677
758
  patientData: PatientData;
678
759
  /** Participant IDs passed to the backend only when creating a new conversation */
679
760
  participantIds?: string[];
761
+ /** Which thread on the case. Defaults to the shared `case` thread. */
762
+ kind?: ConversationKind;
680
763
  /** Max messages to keep in inline state (default 5) */
681
764
  messageLimit?: number;
682
765
  }
@@ -704,7 +787,7 @@ interface UseInlineCollabReturn {
704
787
  * - First sendMessage creates conversation atomically + starts real-time flow
705
788
  * - Subsequent sends go through the socket
706
789
  */
707
- declare function useInlineCollab({ orderId, patientData, participantIds, messageLimit, }: UseInlineCollabOptions): UseInlineCollabReturn;
790
+ declare function useInlineCollab({ orderId, patientData, participantIds, messageLimit, kind, }: UseInlineCollabOptions): UseInlineCollabReturn;
708
791
 
709
792
  interface UseMessagesOptions {
710
793
  conversationId: string | null;
@@ -907,6 +990,13 @@ interface PatientHeaderProps {
907
990
  hideName?: boolean;
908
991
  /** Override the displayed name — use the stored conversation.name to avoid reconstructing from patientData components */
909
992
  displayName?: string;
993
+ /**
994
+ * Render the support variant: a help channel has no patient, no study and no
995
+ * case, so the demographics line and every case-level action are suppressed
996
+ * and the title is the channel name. Without this the header would show a
997
+ * row of empty fields and a dead "View DICOM" button.
998
+ */
999
+ isSupportChannel?: boolean;
910
1000
  className?: string;
911
1001
  }
912
1002
  /**
@@ -924,7 +1014,7 @@ interface PatientHeaderProps {
924
1014
  * When `hideName=true` (CollabPopup wraps the conversation in its own
925
1015
  * window title), only the meta + actions row renders.
926
1016
  */
927
- declare function PatientHeader({ patientData, participants, onOpenDicom, onOpenCase, onOpenSettings, onBack, hideName, displayName, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
1017
+ declare function PatientHeader({ patientData, participants, onOpenDicom, onOpenCase, onOpenSettings, onBack, hideName, displayName, isSupportChannel, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
928
1018
 
929
1019
  interface MessageListHandle {
930
1020
  /** Scroll to a specific message by ID (used by pin jump-to) */
@@ -1187,4 +1277,4 @@ declare const SUPPORTED_IMAGE_TYPES: string[];
1187
1277
  /** Max file size in bytes (20MB) */
1188
1278
  declare const MAX_FILE_SIZE: number;
1189
1279
 
1190
- export { AUDIO_MIME_TYPE, type ChannelEvent, ChannelSettings, type ChannelUpdatePayload, type CollabConfig, type CollabError, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, type Conversation, ConversationList, ConversationListItem, type ConversationListItem$1 as ConversationListItemData, type ConversationPreview, type CreateConversationResponse, DEEP_LINK_PREFIX, EVENTS, type InviteUserPayload, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, type Message, MessageActionsMenu, MessageBubble, MessageInput, MessageList, type MessageListHandle, type MessageSnapshot, type MessageType, type Participant, ParticipantsList, type PatientData, PatientHeader, PinnedMessagesBar, type PreviewBatchResponse, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, type SendMessagePayload, THEME_DEFAULTS, THEME_VAR, TYPING_DEBOUNCE_MS, type Theme, type TypingEvent, type UserRole, applyThemeOverrides, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };
1280
+ export { AUDIO_MIME_TYPE, type ChannelEvent, ChannelSettings, type ChannelUpdatePayload, type CollabConfig, type CollabError, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, type Conversation, type ConversationKind, ConversationList, ConversationListItem, type ConversationListItem$1 as ConversationListItemData, type ConversationPreview, type CreateConversationResponse, DEEP_LINK_PREFIX, EVENTS, type InviteUserPayload, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, type Message, MessageActionsMenu, MessageBubble, MessageInput, MessageList, type MessageListHandle, type MessageSnapshot, type MessageType, type Participant, ParticipantsList, type PatientData, PatientHeader, PinnedMessagesBar, type PreviewBatchResponse, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, type SendMessagePayload, THEME_DEFAULTS, THEME_VAR, TYPING_DEBOUNCE_MS, type Theme, type TypingEvent, type UserRole, applyThemeOverrides, isCaseKind, isHelpKind, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };
package/dist/index.d.ts CHANGED
@@ -118,17 +118,51 @@ interface PatientData {
118
118
  labName?: string;
119
119
  bodyParts?: string[];
120
120
  }
121
+ /**
122
+ * What a thread is about — and, crucially, what it is SCOPED to.
123
+ *
124
+ * - `case` — the discussion about one clinical order. Scoped to an order,
125
+ * so `orderId` is always set. Lab, radiologist, physician, admin.
126
+ * - `help_lab` — an imaging centre's help channel with Natoe support. Scoped to
127
+ * the LAB, not to any case: it has NO `orderId`, it outlives
128
+ * every order, and there is exactly one per imaging centre.
129
+ *
130
+ * `help_lab` was briefly modelled as per-case (one help thread per order). It is
131
+ * now per-lab, so support gets one continuous thread with each imaging centre
132
+ * rather than N short-lived ones scattered across cases. A case can still be
133
+ * referenced from inside the channel; it just no longer scopes it.
134
+ *
135
+ * Isolation — who can see a help channel — is enforced by the backend's
136
+ * participant seeding, never by anything in this package. Treat this as a label
137
+ * for rendering and for addressing the right thread, not as a security boundary.
138
+ *
139
+ * Optional everywhere, defaulting to `case`, so callers written before help
140
+ * channels existed keep their exact previous behaviour.
141
+ */
142
+ type ConversationKind = 'case' | 'help_lab';
143
+ declare function isHelpKind(kind?: ConversationKind | string): boolean;
144
+ /**
145
+ * True when a thread is scoped to an order and therefore has patient context to
146
+ * render. Prefer this over testing `orderId` directly: a help channel has no
147
+ * order, so every patient-shaped affordance must be skipped for it.
148
+ */
149
+ declare function isCaseKind(kind?: ConversationKind | string): boolean;
121
150
  interface Conversation {
122
151
  id: string;
123
- orderId: string;
152
+ /** Absent on a help channel — that thread belongs to a lab, not an order. */
153
+ orderId?: string;
154
+ /** Set only on a help channel: the imaging centre that owns it. */
155
+ labUserId?: string;
124
156
  name: string;
157
+ kind?: ConversationKind;
125
158
  picture?: string;
126
159
  participants: Participant[];
127
160
  lastMessage?: Message;
128
161
  unreadCount: number;
129
162
  createdAt: string;
130
163
  updatedAt: string;
131
- /** Denormalized patient data snapshot — lets UI render without extra fetch */
164
+ /** Denormalized patient data snapshot — lets UI render without extra fetch.
165
+ * Always absent on a help channel: there is no patient. */
132
166
  patientSnapshot?: PatientData;
133
167
  /** Currently pinned messages (max 3) */
134
168
  pinnedMessages?: Message[];
@@ -139,8 +173,13 @@ interface Conversation {
139
173
  */
140
174
  interface ConversationListItem$1 {
141
175
  id: string;
142
- orderId: string;
176
+ /** Absent on a help channel — see `Conversation.orderId`. */
177
+ orderId?: string;
178
+ labUserId?: string;
143
179
  name: string;
180
+ /** Lets the inbox render the lab's help channel distinctly from its case
181
+ * threads — they appear as separate rows. */
182
+ kind?: ConversationKind;
144
183
  picture?: string;
145
184
  unreadCount: number;
146
185
  lastMessage?: Message;
@@ -154,6 +193,7 @@ interface ConversationListItem$1 {
154
193
  interface ConversationPreview {
155
194
  conversationId: string;
156
195
  orderId: string;
196
+ kind?: ConversationKind;
157
197
  messageCount: number;
158
198
  unreadCount: number;
159
199
  /** Last N messages (typically 5) for inline display */
@@ -163,7 +203,9 @@ interface ConversationPreview {
163
203
  }
164
204
  /** Batch preview response shape from the backend */
165
205
  interface PreviewBatchResponse {
166
- /** Key is orderId, value is preview or null if no conversation exists */
206
+ /** Key is orderId, value is preview or null if no conversation exists.
207
+ * Scoped to a single `kind` per request — the backend filters server-side,
208
+ * so this map never mixes a case thread and a help thread for one order. */
167
209
  previews: Record<string, ConversationPreview | null>;
168
210
  }
169
211
  /** Response from createConversationWithMessage (first-message flow) */
@@ -261,12 +303,19 @@ interface InviteUserPayload {
261
303
  }
262
304
 
263
305
  interface CollabPanelProps {
264
- /** Order ID one conversation per study */
265
- orderId: string;
266
- /** Patient/study data displayed in the header */
267
- patientData: PatientData;
306
+ /** Order this thread belongs to. Required for a `case` thread; omitted for a
307
+ * help channel, which belongs to a lab rather than an order. */
308
+ orderId?: string;
309
+ /** A thread the caller already resolved — how a help channel is opened.
310
+ * Skips the order-keyed preview lookup entirely. */
311
+ conversation?: Conversation | null;
312
+ /** Patient/study data displayed in the header. Absent on a help channel:
313
+ * there is no patient, and the header renders its support variant instead. */
314
+ patientData?: PatientData;
268
315
  /** Participant user IDs — only used when creating a brand-new conversation */
269
316
  participantIds?: string[];
317
+ /** Which thread on the case. Defaults to the shared `case` thread. */
318
+ kind?: ConversationKind;
270
319
  /** Show "seen by" indicator under own messages (default true) */
271
320
  showSeenBy?: boolean;
272
321
  /**
@@ -317,7 +366,7 @@ interface CollabPanelProps {
317
366
  * Full collaboration panel: patient header + pinned bar + message thread + input.
318
367
  * Supports reply, pin/unpin, and (optionally) seen-by indicators.
319
368
  */
320
- declare function CollabPanel({ orderId, patientData, participantIds, showSeenBy, onBack, hidePatientName, hideOpenCase, onConversationChange, themeMode, showSettings: controlledShowSettings, onSettingsChange, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
369
+ declare function CollabPanel({ orderId, conversation: providedConversation, patientData, participantIds, kind, showSeenBy, onBack, hidePatientName, hideOpenCase, onConversationChange, themeMode, showSettings: controlledShowSettings, onSettingsChange, className, style, }: CollabPanelProps): react_jsx_runtime.JSX.Element;
321
370
 
322
371
  interface CollabPopupProps {
323
372
  /** Order ID for this conversation */
@@ -326,6 +375,8 @@ interface CollabPopupProps {
326
375
  patientData: PatientData;
327
376
  /** Participant user IDs — only used when creating a brand-new conversation */
328
377
  participantIds?: string[];
378
+ /** Which thread on the case. Defaults to the shared `case` thread. */
379
+ kind?: ConversationKind;
329
380
  /** Whether the popup is open */
330
381
  isOpen: boolean;
331
382
  /** Close the popup */
@@ -355,7 +406,7 @@ interface CollabPopupProps {
355
406
  * Draggable floating chat popup — drop-in replacement for DraggableChatPopup.
356
407
  * Renders a CollabPanel inside a movable, resizable container.
357
408
  */
358
- declare function CollabPopup({ orderId, patientData, participantIds, isOpen, onClose, onBack, onMinimize, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
409
+ declare function CollabPopup({ orderId, patientData, participantIds, kind, isOpen, onClose, onBack, onMinimize, initialPosition, width, height, className, }: CollabPopupProps): react_jsx_runtime.JSX.Element | null;
359
410
 
360
411
  type ColabInlineMode = 'light' | 'dark';
361
412
  interface CollabInlineProps {
@@ -363,6 +414,8 @@ interface CollabInlineProps {
363
414
  patientData: PatientData;
364
415
  /** User IDs to invite when the first message creates the conversation */
365
416
  participantIds?: string[];
417
+ /** Which thread on the case. Defaults to the shared `case` thread. */
418
+ kind?: ConversationKind;
366
419
  /** Called when user clicks the expand button — host opens the floating popup */
367
420
  onExpand?: () => void;
368
421
  /** Max inline messages to show (default 1 — just the latest) */
@@ -390,7 +443,7 @@ interface CollabInlineProps {
390
443
  * Supports text and voice messages. File attachments and full chat features
391
444
  * stay in the floating popup / inbox.
392
445
  */
393
- declare function CollabInline({ orderId, patientData, participantIds, onExpand, messageLimit, placeholder, mode, className, style, }: CollabInlineProps): react_jsx_runtime.JSX.Element;
446
+ declare function CollabInline({ orderId, patientData, participantIds, kind, onExpand, messageLimit, placeholder, mode, className, style, }: CollabInlineProps): react_jsx_runtime.JSX.Element;
394
447
 
395
448
  interface CollabInboxProps {
396
449
  /** Optional: preselect a specific conversation (by conversation ID) */
@@ -552,6 +605,8 @@ interface FetchMessagesOptions {
552
605
  interface CreateConversationOptions {
553
606
  name: string;
554
607
  participantIds?: string[];
608
+ /** Which thread to create. Defaults to the shared case thread. */
609
+ kind?: ConversationKind;
555
610
  }
556
611
  interface CollabContextValue {
557
612
  socket: CollabSocket;
@@ -573,9 +628,9 @@ interface CollabContextValue {
573
628
  */
574
629
  unreadCountsByOrder: Record<string, number>;
575
630
  /** Batched preview lookup — coalesces calls within a microtask */
576
- requestPreview: (orderId: string) => Promise<ConversationPreview | null>;
631
+ requestPreview: (orderId: string, kind?: ConversationKind) => Promise<ConversationPreview | null>;
577
632
  /** Invalidate a cached preview (e.g. when a new message arrives) */
578
- invalidatePreview: (orderId: string) => void;
633
+ invalidatePreview: (orderId: string, kind?: ConversationKind) => void;
579
634
  /** Fetch paginated message history */
580
635
  fetchMessages: (conversationId: string, options?: FetchMessagesOptions) => Promise<Message[]>;
581
636
  /** Atomic create-or-send — creates conversation if missing, then stores the first message */
@@ -603,12 +658,38 @@ interface CollabProviderProps {
603
658
  declare function CollabProvider({ config, apiBaseUrl, children }: CollabProviderProps): react_jsx_runtime.JSX.Element | null;
604
659
 
605
660
  interface UseConversationOptions {
606
- orderId: string;
607
- patientData: PatientData;
661
+ /**
662
+ * The order this thread belongs to. Required for a `case` thread, which is
663
+ * created lazily on first message and resolved by an order-keyed preview.
664
+ * Omitted for a help channel — that belongs to a lab, not an order.
665
+ */
666
+ orderId?: string;
667
+ /**
668
+ * A thread the caller has ALREADY resolved, opened directly instead of being
669
+ * looked up by order.
670
+ *
671
+ * This is how a help channel opens. The backend's `GET /help_channel` does
672
+ * the get-or-create and hands back the conversation, so there is nothing to
673
+ * look up and nothing to lazily create — passing it here skips the preview
674
+ * request entirely. The preview endpoint is order-keyed and could not serve
675
+ * an order-less thread in any case.
676
+ */
677
+ conversation?: Conversation | null;
678
+ /** Patient/study context. Absent on a help channel — there is no patient. */
679
+ patientData?: PatientData;
608
680
  /** Participant user IDs used only when creating a brand-new conversation */
609
681
  participantIds?: string[];
610
682
  /** Load full history on mount (default true) */
611
683
  loadHistory?: boolean;
684
+ /**
685
+ * What this thread is. Defaults to the shared `case` thread, so every
686
+ * existing caller is unaffected.
687
+ *
688
+ * For `help_lab` the backend seats only the imaging centre plus Natoe
689
+ * support, and `participantIds` is ignored server-side — a help channel must
690
+ * never be widened by the client.
691
+ */
692
+ kind?: ConversationKind;
612
693
  }
613
694
  interface UseConversationReturn {
614
695
  conversation: Conversation | null;
@@ -643,7 +724,7 @@ interface UseConversationReturn {
643
724
  * when the user sends the first message. If a conversation already exists for
644
725
  * the given orderId, it's loaded and joined automatically.
645
726
  */
646
- declare function useConversation({ orderId, patientData, participantIds, loadHistory, }: UseConversationOptions): UseConversationReturn;
727
+ declare function useConversation({ orderId, conversation: providedConversation, patientData, participantIds, loadHistory, kind, }: UseConversationOptions): UseConversationReturn;
647
728
 
648
729
  interface UseConversationListOptions {
649
730
  /**
@@ -677,6 +758,8 @@ interface UseInlineCollabOptions {
677
758
  patientData: PatientData;
678
759
  /** Participant IDs passed to the backend only when creating a new conversation */
679
760
  participantIds?: string[];
761
+ /** Which thread on the case. Defaults to the shared `case` thread. */
762
+ kind?: ConversationKind;
680
763
  /** Max messages to keep in inline state (default 5) */
681
764
  messageLimit?: number;
682
765
  }
@@ -704,7 +787,7 @@ interface UseInlineCollabReturn {
704
787
  * - First sendMessage creates conversation atomically + starts real-time flow
705
788
  * - Subsequent sends go through the socket
706
789
  */
707
- declare function useInlineCollab({ orderId, patientData, participantIds, messageLimit, }: UseInlineCollabOptions): UseInlineCollabReturn;
790
+ declare function useInlineCollab({ orderId, patientData, participantIds, messageLimit, kind, }: UseInlineCollabOptions): UseInlineCollabReturn;
708
791
 
709
792
  interface UseMessagesOptions {
710
793
  conversationId: string | null;
@@ -907,6 +990,13 @@ interface PatientHeaderProps {
907
990
  hideName?: boolean;
908
991
  /** Override the displayed name — use the stored conversation.name to avoid reconstructing from patientData components */
909
992
  displayName?: string;
993
+ /**
994
+ * Render the support variant: a help channel has no patient, no study and no
995
+ * case, so the demographics line and every case-level action are suppressed
996
+ * and the title is the channel name. Without this the header would show a
997
+ * row of empty fields and a dead "View DICOM" button.
998
+ */
999
+ isSupportChannel?: boolean;
910
1000
  className?: string;
911
1001
  }
912
1002
  /**
@@ -924,7 +1014,7 @@ interface PatientHeaderProps {
924
1014
  * When `hideName=true` (CollabPopup wraps the conversation in its own
925
1015
  * window title), only the meta + actions row renders.
926
1016
  */
927
- declare function PatientHeader({ patientData, participants, onOpenDicom, onOpenCase, onOpenSettings, onBack, hideName, displayName, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
1017
+ declare function PatientHeader({ patientData, participants, onOpenDicom, onOpenCase, onOpenSettings, onBack, hideName, displayName, isSupportChannel, className, }: PatientHeaderProps): react_jsx_runtime.JSX.Element;
928
1018
 
929
1019
  interface MessageListHandle {
930
1020
  /** Scroll to a specific message by ID (used by pin jump-to) */
@@ -1187,4 +1277,4 @@ declare const SUPPORTED_IMAGE_TYPES: string[];
1187
1277
  /** Max file size in bytes (20MB) */
1188
1278
  declare const MAX_FILE_SIZE: number;
1189
1279
 
1190
- export { AUDIO_MIME_TYPE, type ChannelEvent, ChannelSettings, type ChannelUpdatePayload, type CollabConfig, type CollabError, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, type Conversation, ConversationList, ConversationListItem, type ConversationListItem$1 as ConversationListItemData, type ConversationPreview, type CreateConversationResponse, DEEP_LINK_PREFIX, EVENTS, type InviteUserPayload, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, type Message, MessageActionsMenu, MessageBubble, MessageInput, MessageList, type MessageListHandle, type MessageSnapshot, type MessageType, type Participant, ParticipantsList, type PatientData, PatientHeader, PinnedMessagesBar, type PreviewBatchResponse, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, type SendMessagePayload, THEME_DEFAULTS, THEME_VAR, TYPING_DEBOUNCE_MS, type Theme, type TypingEvent, type UserRole, applyThemeOverrides, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };
1280
+ export { AUDIO_MIME_TYPE, type ChannelEvent, ChannelSettings, type ChannelUpdatePayload, type CollabConfig, type CollabError, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, type Conversation, type ConversationKind, ConversationList, ConversationListItem, type ConversationListItem$1 as ConversationListItemData, type ConversationPreview, type CreateConversationResponse, DEEP_LINK_PREFIX, EVENTS, type InviteUserPayload, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, type Message, MessageActionsMenu, MessageBubble, MessageInput, MessageList, type MessageListHandle, type MessageSnapshot, type MessageType, type Participant, ParticipantsList, type PatientData, PatientHeader, PinnedMessagesBar, type PreviewBatchResponse, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, type SendMessagePayload, THEME_DEFAULTS, THEME_VAR, TYPING_DEBOUNCE_MS, type Theme, type TypingEvent, type UserRole, applyThemeOverrides, isCaseKind, isHelpKind, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };