@natoe/colab 0.1.28 → 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
@@ -119,25 +119,40 @@ interface PatientData {
119
119
  bodyParts?: string[];
120
120
  }
121
121
  /**
122
- * Which thread on a case this is.
122
+ * What a thread is about and, crucially, what it is SCOPED to.
123
123
  *
124
- * - `case` — the shared discussion: lab, radiologist, physician, admin
125
- * - `help_lab` imaging center <-> Natoe support, private to that side
126
- * - `help_radiologist` — assigned radiologist <-> Natoe support, private to that side
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.
127
129
  *
128
- * The two help kinds are mutually invisible. That isolation is enforced by the
129
- * backend's participant seeding, never by anything in this package treat this
130
- * as a label for rendering and for addressing the right thread, not as a
131
- * security boundary.
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.
132
138
  *
133
139
  * Optional everywhere, defaulting to `case`, so callers written before help
134
- * threads existed keep their exact previous behaviour.
140
+ * channels existed keep their exact previous behaviour.
135
141
  */
136
- type ConversationKind = 'case' | 'help_lab' | 'help_radiologist';
142
+ type ConversationKind = 'case' | 'help_lab';
137
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;
138
150
  interface Conversation {
139
151
  id: string;
140
- 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;
141
156
  name: string;
142
157
  kind?: ConversationKind;
143
158
  picture?: string;
@@ -146,7 +161,8 @@ interface Conversation {
146
161
  unreadCount: number;
147
162
  createdAt: string;
148
163
  updatedAt: string;
149
- /** 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. */
150
166
  patientSnapshot?: PatientData;
151
167
  /** Currently pinned messages (max 3) */
152
168
  pinnedMessages?: Message[];
@@ -157,10 +173,12 @@ interface Conversation {
157
173
  */
158
174
  interface ConversationListItem$1 {
159
175
  id: string;
160
- orderId: string;
176
+ /** Absent on a help channel — see `Conversation.orderId`. */
177
+ orderId?: string;
178
+ labUserId?: string;
161
179
  name: string;
162
- /** Lets the inbox label help threads distinctly from the case thread on the
163
- * same patient both appear as separate rows. */
180
+ /** Lets the inbox render the lab's help channel distinctly from its case
181
+ * threadsthey appear as separate rows. */
164
182
  kind?: ConversationKind;
165
183
  picture?: string;
166
184
  unreadCount: number;
@@ -285,10 +303,15 @@ interface InviteUserPayload {
285
303
  }
286
304
 
287
305
  interface CollabPanelProps {
288
- /** Order ID one conversation per study */
289
- orderId: string;
290
- /** Patient/study data displayed in the header */
291
- 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;
292
315
  /** Participant user IDs — only used when creating a brand-new conversation */
293
316
  participantIds?: string[];
294
317
  /** Which thread on the case. Defaults to the shared `case` thread. */
@@ -343,7 +366,7 @@ interface CollabPanelProps {
343
366
  * Full collaboration panel: patient header + pinned bar + message thread + input.
344
367
  * Supports reply, pin/unpin, and (optionally) seen-by indicators.
345
368
  */
346
- declare function CollabPanel({ orderId, patientData, participantIds, kind, 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;
347
370
 
348
371
  interface CollabPopupProps {
349
372
  /** Order ID for this conversation */
@@ -635,19 +658,36 @@ interface CollabProviderProps {
635
658
  declare function CollabProvider({ config, apiBaseUrl, children }: CollabProviderProps): react_jsx_runtime.JSX.Element | null;
636
659
 
637
660
  interface UseConversationOptions {
638
- orderId: string;
639
- 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;
640
680
  /** Participant user IDs used only when creating a brand-new conversation */
641
681
  participantIds?: string[];
642
682
  /** Load full history on mount (default true) */
643
683
  loadHistory?: boolean;
644
684
  /**
645
- * Which thread on the case to open. Defaults to the shared `case` thread,
646
- * so every existing caller is unaffected.
685
+ * What this thread is. Defaults to the shared `case` thread, so every
686
+ * existing caller is unaffected.
647
687
  *
648
- * For a help kind the backend seats only the requesting side plus Natoe
649
- * support, and `participantIds` is ignored server-side — a help thread
650
- * must never be widened by the client.
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.
651
691
  */
652
692
  kind?: ConversationKind;
653
693
  }
@@ -684,7 +724,7 @@ interface UseConversationReturn {
684
724
  * when the user sends the first message. If a conversation already exists for
685
725
  * the given orderId, it's loaded and joined automatically.
686
726
  */
687
- declare function useConversation({ orderId, patientData, participantIds, loadHistory, kind, }: UseConversationOptions): UseConversationReturn;
727
+ declare function useConversation({ orderId, conversation: providedConversation, patientData, participantIds, loadHistory, kind, }: UseConversationOptions): UseConversationReturn;
688
728
 
689
729
  interface UseConversationListOptions {
690
730
  /**
@@ -950,6 +990,13 @@ interface PatientHeaderProps {
950
990
  hideName?: boolean;
951
991
  /** Override the displayed name — use the stored conversation.name to avoid reconstructing from patientData components */
952
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;
953
1000
  className?: string;
954
1001
  }
955
1002
  /**
@@ -967,7 +1014,7 @@ interface PatientHeaderProps {
967
1014
  * When `hideName=true` (CollabPopup wraps the conversation in its own
968
1015
  * window title), only the meta + actions row renders.
969
1016
  */
970
- 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;
971
1018
 
972
1019
  interface MessageListHandle {
973
1020
  /** Scroll to a specific message by ID (used by pin jump-to) */
@@ -1230,4 +1277,4 @@ declare const SUPPORTED_IMAGE_TYPES: string[];
1230
1277
  /** Max file size in bytes (20MB) */
1231
1278
  declare const MAX_FILE_SIZE: number;
1232
1279
 
1233
- 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, isHelpKind, 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
@@ -119,25 +119,40 @@ interface PatientData {
119
119
  bodyParts?: string[];
120
120
  }
121
121
  /**
122
- * Which thread on a case this is.
122
+ * What a thread is about and, crucially, what it is SCOPED to.
123
123
  *
124
- * - `case` — the shared discussion: lab, radiologist, physician, admin
125
- * - `help_lab` imaging center <-> Natoe support, private to that side
126
- * - `help_radiologist` — assigned radiologist <-> Natoe support, private to that side
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.
127
129
  *
128
- * The two help kinds are mutually invisible. That isolation is enforced by the
129
- * backend's participant seeding, never by anything in this package treat this
130
- * as a label for rendering and for addressing the right thread, not as a
131
- * security boundary.
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.
132
138
  *
133
139
  * Optional everywhere, defaulting to `case`, so callers written before help
134
- * threads existed keep their exact previous behaviour.
140
+ * channels existed keep their exact previous behaviour.
135
141
  */
136
- type ConversationKind = 'case' | 'help_lab' | 'help_radiologist';
142
+ type ConversationKind = 'case' | 'help_lab';
137
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;
138
150
  interface Conversation {
139
151
  id: string;
140
- 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;
141
156
  name: string;
142
157
  kind?: ConversationKind;
143
158
  picture?: string;
@@ -146,7 +161,8 @@ interface Conversation {
146
161
  unreadCount: number;
147
162
  createdAt: string;
148
163
  updatedAt: string;
149
- /** 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. */
150
166
  patientSnapshot?: PatientData;
151
167
  /** Currently pinned messages (max 3) */
152
168
  pinnedMessages?: Message[];
@@ -157,10 +173,12 @@ interface Conversation {
157
173
  */
158
174
  interface ConversationListItem$1 {
159
175
  id: string;
160
- orderId: string;
176
+ /** Absent on a help channel — see `Conversation.orderId`. */
177
+ orderId?: string;
178
+ labUserId?: string;
161
179
  name: string;
162
- /** Lets the inbox label help threads distinctly from the case thread on the
163
- * same patient both appear as separate rows. */
180
+ /** Lets the inbox render the lab's help channel distinctly from its case
181
+ * threadsthey appear as separate rows. */
164
182
  kind?: ConversationKind;
165
183
  picture?: string;
166
184
  unreadCount: number;
@@ -285,10 +303,15 @@ interface InviteUserPayload {
285
303
  }
286
304
 
287
305
  interface CollabPanelProps {
288
- /** Order ID one conversation per study */
289
- orderId: string;
290
- /** Patient/study data displayed in the header */
291
- 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;
292
315
  /** Participant user IDs — only used when creating a brand-new conversation */
293
316
  participantIds?: string[];
294
317
  /** Which thread on the case. Defaults to the shared `case` thread. */
@@ -343,7 +366,7 @@ interface CollabPanelProps {
343
366
  * Full collaboration panel: patient header + pinned bar + message thread + input.
344
367
  * Supports reply, pin/unpin, and (optionally) seen-by indicators.
345
368
  */
346
- declare function CollabPanel({ orderId, patientData, participantIds, kind, 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;
347
370
 
348
371
  interface CollabPopupProps {
349
372
  /** Order ID for this conversation */
@@ -635,19 +658,36 @@ interface CollabProviderProps {
635
658
  declare function CollabProvider({ config, apiBaseUrl, children }: CollabProviderProps): react_jsx_runtime.JSX.Element | null;
636
659
 
637
660
  interface UseConversationOptions {
638
- orderId: string;
639
- 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;
640
680
  /** Participant user IDs used only when creating a brand-new conversation */
641
681
  participantIds?: string[];
642
682
  /** Load full history on mount (default true) */
643
683
  loadHistory?: boolean;
644
684
  /**
645
- * Which thread on the case to open. Defaults to the shared `case` thread,
646
- * so every existing caller is unaffected.
685
+ * What this thread is. Defaults to the shared `case` thread, so every
686
+ * existing caller is unaffected.
647
687
  *
648
- * For a help kind the backend seats only the requesting side plus Natoe
649
- * support, and `participantIds` is ignored server-side — a help thread
650
- * must never be widened by the client.
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.
651
691
  */
652
692
  kind?: ConversationKind;
653
693
  }
@@ -684,7 +724,7 @@ interface UseConversationReturn {
684
724
  * when the user sends the first message. If a conversation already exists for
685
725
  * the given orderId, it's loaded and joined automatically.
686
726
  */
687
- declare function useConversation({ orderId, patientData, participantIds, loadHistory, kind, }: UseConversationOptions): UseConversationReturn;
727
+ declare function useConversation({ orderId, conversation: providedConversation, patientData, participantIds, loadHistory, kind, }: UseConversationOptions): UseConversationReturn;
688
728
 
689
729
  interface UseConversationListOptions {
690
730
  /**
@@ -950,6 +990,13 @@ interface PatientHeaderProps {
950
990
  hideName?: boolean;
951
991
  /** Override the displayed name — use the stored conversation.name to avoid reconstructing from patientData components */
952
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;
953
1000
  className?: string;
954
1001
  }
955
1002
  /**
@@ -967,7 +1014,7 @@ interface PatientHeaderProps {
967
1014
  * When `hideName=true` (CollabPopup wraps the conversation in its own
968
1015
  * window title), only the meta + actions row renders.
969
1016
  */
970
- 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;
971
1018
 
972
1019
  interface MessageListHandle {
973
1020
  /** Scroll to a specific message by ID (used by pin jump-to) */
@@ -1230,4 +1277,4 @@ declare const SUPPORTED_IMAGE_TYPES: string[];
1230
1277
  /** Max file size in bytes (20MB) */
1231
1278
  declare const MAX_FILE_SIZE: number;
1232
1279
 
1233
- 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, isHelpKind, 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.js CHANGED
@@ -891,6 +891,7 @@ function cleanChannelName(name) {
891
891
  // src/hooks/useConversation.ts
892
892
  function useConversation({
893
893
  orderId,
894
+ conversation: providedConversation,
894
895
  patientData,
895
896
  participantIds = [],
896
897
  loadHistory = true,
@@ -923,6 +924,7 @@ function useConversation({
923
924
  const markedReadIdsRef = React4.useRef(/* @__PURE__ */ new Set());
924
925
  const buildName = React4.useCallback(
925
926
  () => {
927
+ if (!patientData) return "";
926
928
  const labName = patientData.labName ?? (config.userRole === "lab" ? config.userName : void 0);
927
929
  return buildChannelName({ ...patientData, labName });
928
930
  },
@@ -1022,6 +1024,28 @@ function useConversation({
1022
1024
  setIsLoading(true);
1023
1025
  setError(null);
1024
1026
  try {
1027
+ if (providedConversation) {
1028
+ setConversation(providedConversation);
1029
+ setParticipants(providedConversation.participants ?? []);
1030
+ joinChannel(providedConversation);
1031
+ if (loadHistory) {
1032
+ const history = await fetchMessages(providedConversation.id);
1033
+ if (cancelled) return;
1034
+ setMessages(history);
1035
+ setHasMore(history.length >= MESSAGES_PAGE_SIZE);
1036
+ setPinnedMessages(history.filter((m) => m.isPinned));
1037
+ }
1038
+ if (!cancelled) setIsLoading(false);
1039
+ return;
1040
+ }
1041
+ if (!orderId) {
1042
+ setConversation(null);
1043
+ setMessages([]);
1044
+ setParticipants([]);
1045
+ setHasMore(false);
1046
+ setIsLoading(false);
1047
+ return;
1048
+ }
1025
1049
  const preview = await requestPreview(orderId, kind);
1026
1050
  if (cancelled) return;
1027
1051
  if (!preview) {
@@ -1083,6 +1107,12 @@ function useConversation({
1083
1107
  const ensureConversation = React4.useCallback(
1084
1108
  async (payload) => {
1085
1109
  if (conversation) return { conv: conversation, persistedByCreate: false };
1110
+ if (!orderId) {
1111
+ throw {
1112
+ code: "NO_CONVERSATION",
1113
+ message: "This conversation is not available yet. Please reopen it and try again."
1114
+ };
1115
+ }
1086
1116
  if (ensureConversationInFlight.current) {
1087
1117
  const conv = await ensureConversationInFlight.current;
1088
1118
  return { conv, persistedByCreate: false };
@@ -1570,15 +1600,19 @@ function PatientHeader({
1570
1600
  onBack,
1571
1601
  hideName = false,
1572
1602
  displayName,
1603
+ isSupportChannel = false,
1573
1604
  className
1574
1605
  }) {
1575
- const hasDicom = !!(patientData.studyId && patientData.storageId);
1576
- const resolvedName = resolveDisplayName(patientData, displayName);
1606
+ const hasDicom = !isSupportChannel && !!(patientData.studyId && patientData.storageId);
1607
+ const resolvedName = isSupportChannel ? displayName || patientData.patientName || "Natoe Support" : resolveDisplayName(patientData, displayName);
1577
1608
  const metaParts = [];
1578
- if (patientData.patientAge) metaParts.push(String(patientData.patientAge));
1579
- if (patientData.patientSex) metaParts.push(String(patientData.patientSex));
1580
- if (patientData.studyType) metaParts.push(patientData.studyType);
1581
- if (patientData.bodyParts && patientData.bodyParts.length > 0) {
1609
+ if (isSupportChannel) {
1610
+ metaParts.push("Help and support");
1611
+ }
1612
+ if (!isSupportChannel && patientData.patientAge) metaParts.push(String(patientData.patientAge));
1613
+ if (!isSupportChannel && patientData.patientSex) metaParts.push(String(patientData.patientSex));
1614
+ if (!isSupportChannel && patientData.studyType) metaParts.push(patientData.studyType);
1615
+ if (!isSupportChannel && patientData.bodyParts && patientData.bodyParts.length > 0) {
1582
1616
  metaParts.push(patientData.bodyParts.join(", "));
1583
1617
  }
1584
1618
  const hasPrimaryActions = hasDicom && onOpenDicom || onOpenCase;
@@ -4535,6 +4569,7 @@ var DARK_THEME_OVERRIDES = {
4535
4569
  };
4536
4570
  function CollabPanel({
4537
4571
  orderId,
4572
+ conversation: providedConversation,
4538
4573
  patientData,
4539
4574
  participantIds,
4540
4575
  kind,
@@ -4576,7 +4611,13 @@ function CollabPanel({
4576
4611
  loadMoreMessages,
4577
4612
  pinMessage,
4578
4613
  unpinMessage
4579
- } = useConversation({ orderId, patientData, participantIds, kind });
4614
+ } = useConversation({
4615
+ orderId,
4616
+ conversation: providedConversation,
4617
+ patientData,
4618
+ participantIds,
4619
+ kind
4620
+ });
4580
4621
  React4.useEffect(() => {
4581
4622
  onConversationChange?.(conversation);
4582
4623
  }, [conversation, onConversationChange]);
@@ -4585,11 +4626,11 @@ function CollabPanel({
4585
4626
  });
4586
4627
  const { handleDeepLink } = useDeepLinks();
4587
4628
  const handleOpenDicom = () => {
4588
- if (patientData.studyId && patientData.storageId && config.onOpenDicom) {
4629
+ if (patientData?.studyId && patientData.storageId && config.onOpenDicom) {
4589
4630
  config.onOpenDicom(patientData.studyId, patientData.storageId);
4590
4631
  }
4591
4632
  };
4592
- const handleOpenCase = !hideOpenCase && config.onOpenCase ? () => config.onOpenCase?.(patientData.orderId, patientData.displayOrderId) : void 0;
4633
+ const handleOpenCase = !hideOpenCase && config.onOpenCase && patientData?.orderId ? () => config.onOpenCase?.(patientData.orderId, patientData.displayOrderId) : void 0;
4593
4634
  const handleJumpToMessage = (messageId) => {
4594
4635
  messageListRef.current?.scrollToMessage(messageId);
4595
4636
  };
@@ -4662,7 +4703,8 @@ function CollabPanel({
4662
4703
  /* @__PURE__ */ jsxRuntime.jsx(
4663
4704
  PatientHeader,
4664
4705
  {
4665
- patientData,
4706
+ patientData: patientData ?? { orderId: "", patientName: conversation?.name ?? "Natoe Support" },
4707
+ isSupportChannel: !patientData,
4666
4708
  participants,
4667
4709
  onOpenDicom: handleOpenDicom,
4668
4710
  onOpenCase: handleOpenCase,
@@ -5901,7 +5943,10 @@ function useConversationList(options) {
5901
5943
 
5902
5944
  // src/core/types.ts
5903
5945
  function isHelpKind(kind) {
5904
- return kind === "help_lab" || kind === "help_radiologist";
5946
+ return kind === "help_lab";
5947
+ }
5948
+ function isCaseKind(kind) {
5949
+ return kind === void 0 || kind === "case";
5905
5950
  }
5906
5951
  function ConversationListItem({
5907
5952
  item,
@@ -6537,6 +6582,7 @@ function CollabInbox({
6537
6582
  );
6538
6583
  }
6539
6584
  function buildPatientData(item) {
6585
+ if (isHelpKind(item.kind) || !item.orderId) return void 0;
6540
6586
  const parsed = parseChannelName(item.name, item.orderId);
6541
6587
  if (item.patientSnapshot) {
6542
6588
  return {
@@ -6874,6 +6920,7 @@ exports.THEME_DEFAULTS = THEME_DEFAULTS;
6874
6920
  exports.THEME_VAR = THEME_VAR;
6875
6921
  exports.TYPING_DEBOUNCE_MS = TYPING_DEBOUNCE_MS;
6876
6922
  exports.applyThemeOverrides = applyThemeOverrides;
6923
+ exports.isCaseKind = isCaseKind;
6877
6924
  exports.isHelpKind = isHelpKind;
6878
6925
  exports.useAudioRecorder = useAudioRecorder;
6879
6926
  exports.useChannelSettings = useChannelSettings;