@hipnation-truth/sdk 0.16.1 → 0.16.3
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/react.d.ts +13 -1
- package/dist/react.js +94 -69
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/react.d.ts
CHANGED
|
@@ -248,6 +248,18 @@ declare function useMessages(conversationId: string | null | undefined, options?
|
|
|
248
248
|
* messages, `undefined` while loading or when `userId` is missing.
|
|
249
249
|
*/
|
|
250
250
|
declare function useUnreadCount(userId: string | null | undefined): UseQueryResult<number>;
|
|
251
|
+
/**
|
|
252
|
+
* Server-side aggregate of the user's unread total + count of
|
|
253
|
+
* conversations with at least one unread. Optionally filtered by
|
|
254
|
+
* `providerPhones` (e.g. selected offices) — the aggregate is
|
|
255
|
+
* computed in Convex, no client-side fetch-and-filter.
|
|
256
|
+
*/
|
|
257
|
+
declare function useUnreadAggregate(userId: string | null | undefined, options?: {
|
|
258
|
+
providerPhones?: string[];
|
|
259
|
+
}): UseQueryResult<{
|
|
260
|
+
totalMessages: number;
|
|
261
|
+
conversationCount: number;
|
|
262
|
+
}>;
|
|
251
263
|
/**
|
|
252
264
|
* Subscribe to all notes attached to a conversation, newest first.
|
|
253
265
|
* Replaces the urql Hasura `useConversation_NotesSubscription` in
|
|
@@ -2542,4 +2554,4 @@ interface UseVoicemailUrlResult {
|
|
|
2542
2554
|
*/
|
|
2543
2555
|
declare function useVoicemailUrl(client: TruthClient): UseVoicemailUrlResult;
|
|
2544
2556
|
|
|
2545
|
-
export { ACTIVE_CALL_STATES, type Appointment, type AppointmentListOptions, CONNECTED_CALL_STATES, type ConversationListItem, type ConversationMessage, type ConversationMessageRow, type ConversationNoteRow, type ConversationRow, type ConversationTaskForUserRow, type ConversationTaskRow, type DialpadCallLogRow, type DialpadCallRow, DialpadCallState, type EventPayloadMap, type EventType, type FamilyMemberRow, type Patient, type PatientListOptions, type PatientSearchResult, type PermissionStatus, type Physician$1 as Physician, RINGING_CALL_STATES, TERMINAL_CALL_STATES, type TrackOptions, TruthProvider, type TruthProviderProps, type TruthTrackingContextValue, TruthTrackingProvider, type TruthTrackingProviderProps, type UseActiveCallsOptions, type UseAppointmentListOptions, type UseConversationMessagesOptions, type UseConversationsFilters, type UseMessagesOptions, type UseNotificationsOptions, type UseNotificationsResult, type UsePatientBasicOptions, type UsePatientBasicResult, type UsePatientFamilyMembersInput, type UsePatientListOptions, type UsePatientMedicalOptions, type UsePatientPhotoOptions, type UsePatientSearchOptions, type UseQueryResult, useActiveCalls, useAppointment, useAppointmentByElationId, useAppointments, useConversationByPhonePair, useConversationMessages, useConversationNotes, useConversationNotesByPhonePair, useConversationTasks, useConversationTasksByPhonePair, useConversationTasksForUser, useConversations, useDialpadCallByCallId, useDialpadCallLog, useDialpadCallsForConversation, useMessages, useNotifications, usePatient, usePatientBasic, usePatientByElationId, usePatientByHintId, usePatientFamilyMembers, usePatientMedical, usePatientPhoto, usePatientSearch, usePatients, usePatientsByIds, usePatientsByPhones, usePharmacyByNcpdpId, usePhysicianByElationId, usePhysiciansByElationIds, useRemindersForConversations, useTruth, useUnreadCount, useUserSettings, useVoicemailUrl };
|
|
2557
|
+
export { ACTIVE_CALL_STATES, type Appointment, type AppointmentListOptions, CONNECTED_CALL_STATES, type ConversationListItem, type ConversationMessage, type ConversationMessageRow, type ConversationNoteRow, type ConversationRow, type ConversationTaskForUserRow, type ConversationTaskRow, type DialpadCallLogRow, type DialpadCallRow, DialpadCallState, type EventPayloadMap, type EventType, type FamilyMemberRow, type Patient, type PatientListOptions, type PatientSearchResult, type PermissionStatus, type Physician$1 as Physician, RINGING_CALL_STATES, TERMINAL_CALL_STATES, type TrackOptions, TruthProvider, type TruthProviderProps, type TruthTrackingContextValue, TruthTrackingProvider, type TruthTrackingProviderProps, type UseActiveCallsOptions, type UseAppointmentListOptions, type UseConversationMessagesOptions, type UseConversationsFilters, type UseMessagesOptions, type UseNotificationsOptions, type UseNotificationsResult, type UsePatientBasicOptions, type UsePatientBasicResult, type UsePatientFamilyMembersInput, type UsePatientListOptions, type UsePatientMedicalOptions, type UsePatientPhotoOptions, type UsePatientSearchOptions, type UseQueryResult, useActiveCalls, useAppointment, useAppointmentByElationId, useAppointments, useConversationByPhonePair, useConversationMessages, useConversationNotes, useConversationNotesByPhonePair, useConversationTasks, useConversationTasksByPhonePair, useConversationTasksForUser, useConversations, useDialpadCallByCallId, useDialpadCallLog, useDialpadCallsForConversation, useMessages, useNotifications, usePatient, usePatientBasic, usePatientByElationId, usePatientByHintId, usePatientFamilyMembers, usePatientMedical, usePatientPhoto, usePatientSearch, usePatients, usePatientsByIds, usePatientsByPhones, usePharmacyByNcpdpId, usePhysicianByElationId, usePhysiciansByElationIds, useRemindersForConversations, useTruth, useUnreadAggregate, useUnreadCount, useUserSettings, useVoicemailUrl };
|
package/dist/react.js
CHANGED
|
@@ -96,6 +96,7 @@ __export(react_exports, {
|
|
|
96
96
|
usePhysiciansByElationIds: () => usePhysiciansByElationIds,
|
|
97
97
|
useRemindersForConversations: () => useRemindersForConversations,
|
|
98
98
|
useTruth: () => useTruth,
|
|
99
|
+
useUnreadAggregate: () => useUnreadAggregate,
|
|
99
100
|
useUnreadCount: () => useUnreadCount,
|
|
100
101
|
useUserSettings: () => useUserSettings,
|
|
101
102
|
useVoicemailUrl: () => useVoicemailUrl
|
|
@@ -183,9 +184,11 @@ function useDialpadCallLog(callId, options) {
|
|
|
183
184
|
// src/react/conversations.ts
|
|
184
185
|
var import_react2 = require("convex/react");
|
|
185
186
|
var import_server2 = require("convex/server");
|
|
187
|
+
var import_react3 = require("react");
|
|
186
188
|
var conversationsListForUserRef = (0, import_server2.makeFunctionReference)("conversations:listForUser");
|
|
187
189
|
var conversationsSearchForUserRef = (0, import_server2.makeFunctionReference)("conversations:searchForUser");
|
|
188
190
|
var conversationsGetUnreadTotalForUserRef = (0, import_server2.makeFunctionReference)("conversations:getUnreadTotalForUser");
|
|
191
|
+
var conversationsGetUnreadAggregateForUserRef = (0, import_server2.makeFunctionReference)("conversations:getUnreadAggregateForUser");
|
|
189
192
|
var conversationsGetByPhonePairRef = (0, import_server2.makeFunctionReference)("conversations:getByPhonePair");
|
|
190
193
|
var conversationMessagesGetByConversationIdRef = (0, import_server2.makeFunctionReference)("conversationMessages:getByConversationId");
|
|
191
194
|
var conversationNotesListForConversationRef = (0, import_server2.makeFunctionReference)("conversationNotes:listForConversation");
|
|
@@ -253,6 +256,27 @@ function useUnreadCount(userId) {
|
|
|
253
256
|
);
|
|
254
257
|
return toResult2(result, skipped);
|
|
255
258
|
}
|
|
259
|
+
function useUnreadAggregate(userId, options) {
|
|
260
|
+
const skipped = !userId;
|
|
261
|
+
const phones = options == null ? void 0 : options.providerPhones;
|
|
262
|
+
const stablePhones = useMemoizedPhones(phones);
|
|
263
|
+
const result = (0, import_react2.useQuery)(
|
|
264
|
+
conversationsGetUnreadAggregateForUserRef,
|
|
265
|
+
skipped ? SKIP2 : {
|
|
266
|
+
userId,
|
|
267
|
+
providerPhones: stablePhones
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
return toResult2(result, skipped);
|
|
271
|
+
}
|
|
272
|
+
function useMemoizedPhones(phones) {
|
|
273
|
+
const key = phones ? [...phones].sort().join("|") : "";
|
|
274
|
+
return (0, import_react3.useMemo)(
|
|
275
|
+
() => phones && phones.length ? [...phones].sort() : void 0,
|
|
276
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
277
|
+
[key]
|
|
278
|
+
);
|
|
279
|
+
}
|
|
256
280
|
function useConversationNotes(conversationId) {
|
|
257
281
|
const skipped = !conversationId;
|
|
258
282
|
const result = (0, import_react2.useQuery)(
|
|
@@ -295,8 +319,8 @@ function useConversationTasksByPhonePair(phonePair) {
|
|
|
295
319
|
}
|
|
296
320
|
|
|
297
321
|
// src/react/hooks.ts
|
|
298
|
-
var
|
|
299
|
-
var
|
|
322
|
+
var import_react4 = require("convex/react");
|
|
323
|
+
var import_react5 = require("react");
|
|
300
324
|
var import_server3 = require("convex/server");
|
|
301
325
|
var patientsListRef = (0, import_server3.makeFunctionReference)("patients:list");
|
|
302
326
|
var patientsGetRef = (0, import_server3.makeFunctionReference)("patients:get");
|
|
@@ -306,45 +330,45 @@ var appointmentsListRef = (0, import_server3.makeFunctionReference)("appointment
|
|
|
306
330
|
var appointmentsGetRef = (0, import_server3.makeFunctionReference)("appointments:get");
|
|
307
331
|
var appointmentsByElationIdRef = (0, import_server3.makeFunctionReference)("appointments:getByElationId");
|
|
308
332
|
function usePatients(options) {
|
|
309
|
-
return (0,
|
|
333
|
+
return (0, import_react4.useQuery)(patientsListRef, options != null ? options : {});
|
|
310
334
|
}
|
|
311
335
|
function usePatient(id) {
|
|
312
|
-
return (0,
|
|
336
|
+
return (0, import_react4.useQuery)(patientsGetRef, { id });
|
|
313
337
|
}
|
|
314
338
|
function usePatientByElationId(elationId) {
|
|
315
|
-
return (0,
|
|
339
|
+
return (0, import_react4.useQuery)(patientsByElationIdRef, {
|
|
316
340
|
elationId
|
|
317
341
|
});
|
|
318
342
|
}
|
|
319
343
|
function usePatientByHintId(hintId) {
|
|
320
|
-
return (0,
|
|
344
|
+
return (0, import_react4.useQuery)(patientsByHintIdRef, {
|
|
321
345
|
hintId
|
|
322
346
|
});
|
|
323
347
|
}
|
|
324
348
|
function useAppointments(options) {
|
|
325
|
-
return (0,
|
|
349
|
+
return (0, import_react4.useQuery)(
|
|
326
350
|
appointmentsListRef,
|
|
327
351
|
options != null ? options : {}
|
|
328
352
|
);
|
|
329
353
|
}
|
|
330
354
|
function useAppointment(id) {
|
|
331
|
-
return (0,
|
|
355
|
+
return (0, import_react4.useQuery)(appointmentsGetRef, { id });
|
|
332
356
|
}
|
|
333
357
|
function useAppointmentByElationId(elationId) {
|
|
334
|
-
return (0,
|
|
358
|
+
return (0, import_react4.useQuery)(appointmentsByElationIdRef, {
|
|
335
359
|
elationId
|
|
336
360
|
});
|
|
337
361
|
}
|
|
338
362
|
var physiciansGetByElationIdsRef = (0, import_server3.makeFunctionReference)("physicians:getByElationIds");
|
|
339
363
|
var physiciansGetByElationIdRef = (0, import_server3.makeFunctionReference)("physicians:getByElationId");
|
|
340
364
|
function usePhysiciansByElationIds(ids) {
|
|
341
|
-
return (0,
|
|
365
|
+
return (0, import_react4.useQuery)(
|
|
342
366
|
physiciansGetByElationIdsRef,
|
|
343
367
|
ids && ids.length > 0 ? { ids } : "skip"
|
|
344
368
|
);
|
|
345
369
|
}
|
|
346
370
|
function usePhysicianByElationId(id) {
|
|
347
|
-
return (0,
|
|
371
|
+
return (0, import_react4.useQuery)(
|
|
348
372
|
physiciansGetByElationIdRef,
|
|
349
373
|
id !== void 0 ? { id } : "skip"
|
|
350
374
|
);
|
|
@@ -354,23 +378,23 @@ var problemsByPatientRef = (0, import_server3.makeFunctionReference)("medicalRec
|
|
|
354
378
|
var allergiesByPatientRef = (0, import_server3.makeFunctionReference)("medicalRecords:getAllergiesByElationPatient");
|
|
355
379
|
var appointmentsByPatientRef = (0, import_server3.makeFunctionReference)("medicalRecords:getAppointmentsByElationPatient");
|
|
356
380
|
function usePatientMedical(elationId, options) {
|
|
357
|
-
const medications = (0,
|
|
381
|
+
const medications = (0, import_react4.useQuery)(
|
|
358
382
|
medicationsByPatientRef,
|
|
359
383
|
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
360
384
|
);
|
|
361
|
-
const problems = (0,
|
|
385
|
+
const problems = (0, import_react4.useQuery)(
|
|
362
386
|
problemsByPatientRef,
|
|
363
387
|
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
364
388
|
);
|
|
365
|
-
const allergies = (0,
|
|
389
|
+
const allergies = (0, import_react4.useQuery)(
|
|
366
390
|
allergiesByPatientRef,
|
|
367
391
|
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
368
392
|
);
|
|
369
|
-
const appointments = (0,
|
|
393
|
+
const appointments = (0, import_react4.useQuery)(
|
|
370
394
|
appointmentsByPatientRef,
|
|
371
395
|
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
372
396
|
);
|
|
373
|
-
(0,
|
|
397
|
+
(0, import_react5.useEffect)(() => {
|
|
374
398
|
if (elationId === void 0 || (options == null ? void 0 : options.skipRefresh)) {
|
|
375
399
|
return;
|
|
376
400
|
}
|
|
@@ -400,15 +424,15 @@ var pharmacyByNcpdpRef = (0, import_server3.makeFunctionReference)("elationPharm
|
|
|
400
424
|
var patientPhotoByIdRef = (0, import_server3.makeFunctionReference)("elationPatientPhotos:getByElationPatientId");
|
|
401
425
|
function usePatientBasic(input, options) {
|
|
402
426
|
var _a, _b;
|
|
403
|
-
const elationRow = (0,
|
|
427
|
+
const elationRow = (0, import_react4.useQuery)(
|
|
404
428
|
elationPatientByIdRef,
|
|
405
429
|
input.elationId !== void 0 ? { elationId: input.elationId } : "skip"
|
|
406
430
|
);
|
|
407
|
-
const hintRow = (0,
|
|
431
|
+
const hintRow = (0, import_react4.useQuery)(
|
|
408
432
|
hintPatientByIdRef,
|
|
409
433
|
input.hintId !== void 0 ? { hintId: input.hintId } : "skip"
|
|
410
434
|
);
|
|
411
|
-
(0,
|
|
435
|
+
(0, import_react5.useEffect)(() => {
|
|
412
436
|
if (options == null ? void 0 : options.skipRefresh) {
|
|
413
437
|
return;
|
|
414
438
|
}
|
|
@@ -455,17 +479,17 @@ function usePatientBasic(input, options) {
|
|
|
455
479
|
};
|
|
456
480
|
}
|
|
457
481
|
function usePharmacyByNcpdpId(ncpdpId) {
|
|
458
|
-
return (0,
|
|
482
|
+
return (0, import_react4.useQuery)(
|
|
459
483
|
pharmacyByNcpdpRef,
|
|
460
484
|
ncpdpId ? { ncpdpId } : "skip"
|
|
461
485
|
);
|
|
462
486
|
}
|
|
463
487
|
function usePatientPhoto(elationId, options) {
|
|
464
|
-
const photo = (0,
|
|
488
|
+
const photo = (0, import_react4.useQuery)(
|
|
465
489
|
patientPhotoByIdRef,
|
|
466
490
|
elationId !== void 0 ? { elationPatientId: elationId } : "skip"
|
|
467
491
|
);
|
|
468
|
-
(0,
|
|
492
|
+
(0, import_react5.useEffect)(() => {
|
|
469
493
|
if (options == null ? void 0 : options.skipRefresh) {
|
|
470
494
|
return;
|
|
471
495
|
}
|
|
@@ -496,7 +520,7 @@ var messagesByPhonesRef = (0, import_server3.makeFunctionReference)("conversatio
|
|
|
496
520
|
var messagesByConversationIdRef = (0, import_server3.makeFunctionReference)("conversationMessages:getByConversationId");
|
|
497
521
|
function useConversationMessages(input, options) {
|
|
498
522
|
const hasPair = !!input.phoneA && !!input.phoneB;
|
|
499
|
-
const byPair = (0,
|
|
523
|
+
const byPair = (0, import_react4.useQuery)(
|
|
500
524
|
messagesByPhonesRef,
|
|
501
525
|
hasPair ? {
|
|
502
526
|
phoneA: input.phoneA,
|
|
@@ -504,7 +528,7 @@ function useConversationMessages(input, options) {
|
|
|
504
528
|
limit: options == null ? void 0 : options.limit
|
|
505
529
|
} : "skip"
|
|
506
530
|
);
|
|
507
|
-
const byConvo = (0,
|
|
531
|
+
const byConvo = (0, import_react4.useQuery)(
|
|
508
532
|
messagesByConversationIdRef,
|
|
509
533
|
!hasPair && input.conversationId ? { conversationId: input.conversationId, limit: options == null ? void 0 : options.limit } : "skip"
|
|
510
534
|
);
|
|
@@ -512,7 +536,7 @@ function useConversationMessages(input, options) {
|
|
|
512
536
|
}
|
|
513
537
|
|
|
514
538
|
// src/react/notifications.ts
|
|
515
|
-
var
|
|
539
|
+
var import_react6 = require("react");
|
|
516
540
|
|
|
517
541
|
// src/web-push.ts
|
|
518
542
|
function isWebPushSupported() {
|
|
@@ -571,12 +595,12 @@ function loadExpo() {
|
|
|
571
595
|
}
|
|
572
596
|
function useNotifications(options) {
|
|
573
597
|
var _a;
|
|
574
|
-
const [permissionStatus, setPermissionStatus] = (0,
|
|
575
|
-
const [devicePushToken, setDevicePushToken] = (0,
|
|
576
|
-
const expoRef = (0,
|
|
577
|
-
const isWebRef = (0,
|
|
578
|
-
const vapidKeyRef = (0,
|
|
579
|
-
(0,
|
|
598
|
+
const [permissionStatus, setPermissionStatus] = (0, import_react6.useState)("unknown");
|
|
599
|
+
const [devicePushToken, setDevicePushToken] = (0, import_react6.useState)(null);
|
|
600
|
+
const expoRef = (0, import_react6.useRef)(null);
|
|
601
|
+
const isWebRef = (0, import_react6.useRef)(false);
|
|
602
|
+
const vapidKeyRef = (0, import_react6.useRef)((_a = options.vapidPublicKey) != null ? _a : null);
|
|
603
|
+
(0, import_react6.useEffect)(() => {
|
|
580
604
|
let mounted = true;
|
|
581
605
|
void (() => __async(null, null, function* () {
|
|
582
606
|
var _a2;
|
|
@@ -632,7 +656,7 @@ function useNotifications(options) {
|
|
|
632
656
|
mounted = false;
|
|
633
657
|
};
|
|
634
658
|
}, [options.apiBaseUrl, options.apiKey]);
|
|
635
|
-
const register = (0,
|
|
659
|
+
const register = (0, import_react6.useCallback)(() => __async(null, null, function* () {
|
|
636
660
|
var _a2, _b;
|
|
637
661
|
if (!options.userId) {
|
|
638
662
|
return { ok: false, reason: "missing_userId" };
|
|
@@ -742,7 +766,7 @@ function useNotifications(options) {
|
|
|
742
766
|
options.appVersion,
|
|
743
767
|
options.serviceWorkerPath
|
|
744
768
|
]);
|
|
745
|
-
const unregister = (0,
|
|
769
|
+
const unregister = (0, import_react6.useCallback)(() => __async(null, null, function* () {
|
|
746
770
|
if (!devicePushToken) {
|
|
747
771
|
return;
|
|
748
772
|
}
|
|
@@ -757,7 +781,7 @@ function useNotifications(options) {
|
|
|
757
781
|
});
|
|
758
782
|
setDevicePushToken(null);
|
|
759
783
|
}), [options.apiBaseUrl, options.apiKey, devicePushToken]);
|
|
760
|
-
const addReceivedListener = (0,
|
|
784
|
+
const addReceivedListener = (0, import_react6.useCallback)(
|
|
761
785
|
(listener) => {
|
|
762
786
|
if (isWebRef.current) {
|
|
763
787
|
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
@@ -786,7 +810,7 @@ function useNotifications(options) {
|
|
|
786
810
|
},
|
|
787
811
|
[]
|
|
788
812
|
);
|
|
789
|
-
const addResponseListener = (0,
|
|
813
|
+
const addResponseListener = (0, import_react6.useCallback)(
|
|
790
814
|
(listener) => {
|
|
791
815
|
if (isWebRef.current) {
|
|
792
816
|
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
@@ -815,7 +839,7 @@ function useNotifications(options) {
|
|
|
815
839
|
},
|
|
816
840
|
[]
|
|
817
841
|
);
|
|
818
|
-
const getBadgeCount = (0,
|
|
842
|
+
const getBadgeCount = (0, import_react6.useCallback)(() => __async(null, null, function* () {
|
|
819
843
|
var _a2;
|
|
820
844
|
const expo = expoRef.current;
|
|
821
845
|
if (!(expo == null ? void 0 : expo.getBadgeCountAsync)) {
|
|
@@ -823,7 +847,7 @@ function useNotifications(options) {
|
|
|
823
847
|
}
|
|
824
848
|
return (_a2 = yield expo.getBadgeCountAsync()) != null ? _a2 : 0;
|
|
825
849
|
}), []);
|
|
826
|
-
const setBadgeCount = (0,
|
|
850
|
+
const setBadgeCount = (0, import_react6.useCallback)((count) => __async(null, null, function* () {
|
|
827
851
|
const expo = expoRef.current;
|
|
828
852
|
if (!(expo == null ? void 0 : expo.setBadgeCountAsync)) {
|
|
829
853
|
return;
|
|
@@ -831,7 +855,7 @@ function useNotifications(options) {
|
|
|
831
855
|
yield expo.setBadgeCountAsync(count);
|
|
832
856
|
}), []);
|
|
833
857
|
const autoRegister = options.autoRegister !== false;
|
|
834
|
-
(0,
|
|
858
|
+
(0, import_react6.useEffect)(() => {
|
|
835
859
|
if (!autoRegister) {
|
|
836
860
|
return;
|
|
837
861
|
}
|
|
@@ -889,7 +913,7 @@ function detectPlatform(tokenType) {
|
|
|
889
913
|
}
|
|
890
914
|
|
|
891
915
|
// src/react/patient-family.ts
|
|
892
|
-
var
|
|
916
|
+
var import_react7 = require("convex/react");
|
|
893
917
|
var import_server4 = require("convex/server");
|
|
894
918
|
var patientsFamilyMembersRef = (0, import_server4.makeFunctionReference)("patients:listFamilyMembers");
|
|
895
919
|
var SKIP3 = "skip";
|
|
@@ -898,7 +922,7 @@ function usePatientFamilyMembers(input) {
|
|
|
898
922
|
const hasPhoneNumbers = !!((input == null ? void 0 : input.phoneNumbers) && input.phoneNumbers.length > 0);
|
|
899
923
|
const shouldQuery = hasFamilyId || hasPhoneNumbers;
|
|
900
924
|
const args = shouldQuery ? __spreadValues(__spreadValues(__spreadValues({}, (input == null ? void 0 : input.familyId) ? { familyId: input.familyId } : {}), (input == null ? void 0 : input.phoneNumbers) && input.phoneNumbers.length > 0 ? { phoneNumbers: input.phoneNumbers } : {}), (input == null ? void 0 : input.excludeHintId) ? { excludeHintId: input.excludeHintId } : {}) : SKIP3;
|
|
901
|
-
const result = (0,
|
|
925
|
+
const result = (0, import_react7.useQuery)(
|
|
902
926
|
patientsFamilyMembersRef,
|
|
903
927
|
args
|
|
904
928
|
);
|
|
@@ -913,7 +937,7 @@ function usePatientFamilyMembers(input) {
|
|
|
913
937
|
}
|
|
914
938
|
|
|
915
939
|
// src/react/patient-search.ts
|
|
916
|
-
var
|
|
940
|
+
var import_react8 = require("convex/react");
|
|
917
941
|
var import_server5 = require("convex/server");
|
|
918
942
|
var patientsSearchRef = (0, import_server5.makeFunctionReference)("patients:search");
|
|
919
943
|
var SKIP4 = "skip";
|
|
@@ -921,7 +945,7 @@ function usePatientSearch(options) {
|
|
|
921
945
|
var _a;
|
|
922
946
|
const trimmedQuery = ((_a = options.query) != null ? _a : "").trim();
|
|
923
947
|
const skipped = trimmedQuery.length === 0;
|
|
924
|
-
const result = (0,
|
|
948
|
+
const result = (0, import_react8.useQuery)(
|
|
925
949
|
patientsSearchRef,
|
|
926
950
|
skipped ? SKIP4 : __spreadValues(__spreadValues(__spreadValues({
|
|
927
951
|
query: trimmedQuery
|
|
@@ -938,23 +962,23 @@ function usePatientSearch(options) {
|
|
|
938
962
|
}
|
|
939
963
|
|
|
940
964
|
// src/react/patients-bulk.ts
|
|
941
|
-
var
|
|
965
|
+
var import_react9 = require("convex/react");
|
|
942
966
|
var import_server6 = require("convex/server");
|
|
943
|
-
var
|
|
967
|
+
var import_react10 = require("react");
|
|
944
968
|
var patientsGetByIdsRef = (0, import_server6.makeFunctionReference)("patients:getByIds");
|
|
945
969
|
var patientsGetByPhonesRef = (0, import_server6.makeFunctionReference)("patients:getByPhones");
|
|
946
970
|
var SKIP5 = "skip";
|
|
947
971
|
function usePatientsByIds(ids) {
|
|
948
|
-
const stableIds = (0,
|
|
972
|
+
const stableIds = (0, import_react10.useMemo)(() => {
|
|
949
973
|
const arr = ids != null ? ids : [];
|
|
950
974
|
return [...new Set(arr)].sort();
|
|
951
975
|
}, [ids]);
|
|
952
976
|
const skipped = stableIds.length === 0;
|
|
953
|
-
const result = (0,
|
|
977
|
+
const result = (0, import_react9.useQuery)(
|
|
954
978
|
patientsGetByIdsRef,
|
|
955
979
|
skipped ? SKIP5 : { ids: stableIds }
|
|
956
980
|
);
|
|
957
|
-
const mapped = (0,
|
|
981
|
+
const mapped = (0, import_react10.useMemo)(() => {
|
|
958
982
|
if (result === void 0) return void 0;
|
|
959
983
|
return Object.fromEntries(
|
|
960
984
|
result.map((p) => {
|
|
@@ -974,17 +998,17 @@ function usePatientsByIds(ids) {
|
|
|
974
998
|
};
|
|
975
999
|
}
|
|
976
1000
|
function usePatientsByPhones(phones) {
|
|
977
|
-
const stableDigits = (0,
|
|
1001
|
+
const stableDigits = (0, import_react10.useMemo)(() => {
|
|
978
1002
|
const arr = phones != null ? phones : [];
|
|
979
1003
|
const digits = arr.map((p) => p.replace(/\D+/g, "")).filter((s) => s.length > 0);
|
|
980
1004
|
return [...new Set(digits)].sort();
|
|
981
1005
|
}, [phones]);
|
|
982
1006
|
const skipped = stableDigits.length === 0;
|
|
983
|
-
const result = (0,
|
|
1007
|
+
const result = (0, import_react9.useQuery)(
|
|
984
1008
|
patientsGetByPhonesRef,
|
|
985
1009
|
skipped ? SKIP5 : { phoneDigits: stableDigits }
|
|
986
1010
|
);
|
|
987
|
-
const mapped = (0,
|
|
1011
|
+
const mapped = (0, import_react10.useMemo)(() => {
|
|
988
1012
|
if (result === void 0) return void 0;
|
|
989
1013
|
return Object.fromEntries(result.map((r) => [r.phoneDigits, r.patient]));
|
|
990
1014
|
}, [result]);
|
|
@@ -999,8 +1023,8 @@ function usePatientsByPhones(phones) {
|
|
|
999
1023
|
}
|
|
1000
1024
|
|
|
1001
1025
|
// src/react/provider.ts
|
|
1002
|
-
var
|
|
1003
|
-
var
|
|
1026
|
+
var import_react11 = require("convex/react");
|
|
1027
|
+
var import_react12 = require("react");
|
|
1004
1028
|
var CONVEX_URLS = {
|
|
1005
1029
|
local: "https://courteous-duck-623.convex.cloud",
|
|
1006
1030
|
staging: "https://courteous-duck-623.convex.cloud",
|
|
@@ -1023,19 +1047,19 @@ function TruthProvider({
|
|
|
1023
1047
|
children
|
|
1024
1048
|
}) {
|
|
1025
1049
|
const url = resolveConvexUrl(environment, convexUrl);
|
|
1026
|
-
const client = (0,
|
|
1027
|
-
return (0,
|
|
1050
|
+
const client = (0, import_react12.useMemo)(() => new import_react11.ConvexReactClient(url), [url]);
|
|
1051
|
+
return (0, import_react12.createElement)(import_react11.ConvexProvider, { client }, children);
|
|
1028
1052
|
}
|
|
1029
1053
|
|
|
1030
1054
|
// src/react/reminders.ts
|
|
1031
|
-
var
|
|
1055
|
+
var import_react13 = require("convex/react");
|
|
1032
1056
|
var import_server7 = require("convex/server");
|
|
1033
1057
|
var remindersListPendingByConversationIdsRef = (0, import_server7.makeFunctionReference)("reminders:listPendingByConversationIds");
|
|
1034
1058
|
var SKIP6 = "skip";
|
|
1035
1059
|
function useRemindersForConversations(conversationIds) {
|
|
1036
1060
|
const ids = conversationIds != null ? conversationIds : [];
|
|
1037
1061
|
const skipped = ids.length === 0;
|
|
1038
|
-
const result = (0,
|
|
1062
|
+
const result = (0, import_react13.useQuery)(
|
|
1039
1063
|
remindersListPendingByConversationIdsRef,
|
|
1040
1064
|
skipped ? SKIP6 : { conversationIds: ids }
|
|
1041
1065
|
);
|
|
@@ -1051,7 +1075,7 @@ function useRemindersForConversations(conversationIds) {
|
|
|
1051
1075
|
}
|
|
1052
1076
|
|
|
1053
1077
|
// src/react/tracking.ts
|
|
1054
|
-
var
|
|
1078
|
+
var import_react14 = require("react");
|
|
1055
1079
|
|
|
1056
1080
|
// src/tracking/tracker.ts
|
|
1057
1081
|
function generateUuidV7() {
|
|
@@ -1246,7 +1270,7 @@ function sleep(ms) {
|
|
|
1246
1270
|
}
|
|
1247
1271
|
|
|
1248
1272
|
// src/react/tracking.ts
|
|
1249
|
-
var TruthTrackingContext = (0,
|
|
1273
|
+
var TruthTrackingContext = (0, import_react14.createContext)(
|
|
1250
1274
|
null
|
|
1251
1275
|
);
|
|
1252
1276
|
function TruthTrackingProvider({
|
|
@@ -1257,7 +1281,7 @@ function TruthTrackingProvider({
|
|
|
1257
1281
|
apiKey = "",
|
|
1258
1282
|
children
|
|
1259
1283
|
}) {
|
|
1260
|
-
const value = (0,
|
|
1284
|
+
const value = (0, import_react14.useMemo)(() => {
|
|
1261
1285
|
const tracker = new Tracker({
|
|
1262
1286
|
apiKey,
|
|
1263
1287
|
environment,
|
|
@@ -1276,10 +1300,10 @@ function TruthTrackingProvider({
|
|
|
1276
1300
|
}
|
|
1277
1301
|
};
|
|
1278
1302
|
}, [apiKey, environment, source, sourceVersion, tenantId]);
|
|
1279
|
-
return (0,
|
|
1303
|
+
return (0, import_react14.createElement)(TruthTrackingContext.Provider, { value }, children);
|
|
1280
1304
|
}
|
|
1281
1305
|
function useTruth() {
|
|
1282
|
-
const ctx = (0,
|
|
1306
|
+
const ctx = (0, import_react14.useContext)(TruthTrackingContext);
|
|
1283
1307
|
if (!ctx) {
|
|
1284
1308
|
throw new Error("useTruth must be used within a TruthTrackingProvider");
|
|
1285
1309
|
}
|
|
@@ -1287,13 +1311,13 @@ function useTruth() {
|
|
|
1287
1311
|
}
|
|
1288
1312
|
|
|
1289
1313
|
// src/react/user-settings.ts
|
|
1290
|
-
var
|
|
1314
|
+
var import_react15 = require("convex/react");
|
|
1291
1315
|
var import_server8 = require("convex/server");
|
|
1292
1316
|
var userSettingsGetByUserIdRef = (0, import_server8.makeFunctionReference)("userSettings:getByUserId");
|
|
1293
1317
|
var SKIP7 = "skip";
|
|
1294
1318
|
function useUserSettings(userId) {
|
|
1295
1319
|
const skip = !userId;
|
|
1296
|
-
const result = (0,
|
|
1320
|
+
const result = (0, import_react15.useQuery)(
|
|
1297
1321
|
userSettingsGetByUserIdRef,
|
|
1298
1322
|
skip ? SKIP7 : { userId }
|
|
1299
1323
|
);
|
|
@@ -1308,13 +1332,13 @@ function useUserSettings(userId) {
|
|
|
1308
1332
|
}
|
|
1309
1333
|
|
|
1310
1334
|
// src/react/voicemail.ts
|
|
1311
|
-
var
|
|
1335
|
+
var import_react16 = require("react");
|
|
1312
1336
|
function useVoicemailUrl(client) {
|
|
1313
|
-
const [url, setUrl] = (0,
|
|
1314
|
-
const [isLoading, setIsLoading] = (0,
|
|
1315
|
-
const [error, setError] = (0,
|
|
1316
|
-
const inFlightRef = (0,
|
|
1317
|
-
const fetchUrl = (0,
|
|
1337
|
+
const [url, setUrl] = (0, import_react16.useState)(null);
|
|
1338
|
+
const [isLoading, setIsLoading] = (0, import_react16.useState)(false);
|
|
1339
|
+
const [error, setError] = (0, import_react16.useState)(null);
|
|
1340
|
+
const inFlightRef = (0, import_react16.useRef)(false);
|
|
1341
|
+
const fetchUrl = (0, import_react16.useCallback)(
|
|
1318
1342
|
(voicemailLink) => __async(null, null, function* () {
|
|
1319
1343
|
if (inFlightRef.current) return null;
|
|
1320
1344
|
inFlightRef.current = true;
|
|
@@ -1379,6 +1403,7 @@ function useVoicemailUrl(client) {
|
|
|
1379
1403
|
usePhysiciansByElationIds,
|
|
1380
1404
|
useRemindersForConversations,
|
|
1381
1405
|
useTruth,
|
|
1406
|
+
useUnreadAggregate,
|
|
1382
1407
|
useUnreadCount,
|
|
1383
1408
|
useUserSettings,
|
|
1384
1409
|
useVoicemailUrl
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.ts","../src/react/calls.ts","../src/react/conversations.ts","../src/react/hooks.ts","../src/react/notifications.ts","../src/web-push.ts","../src/react/patient-family.ts","../src/react/patient-search.ts","../src/react/patients-bulk.ts","../src/react/provider.ts","../src/react/reminders.ts","../src/react/tracking.ts","../src/tracking/tracker.ts","../src/react/user-settings.ts","../src/react/voicemail.ts"],"sourcesContent":["/**\n * @hipnation-truth/sdk/react\n *\n * React hooks and provider for Truth SDK — real-time Convex-backed data.\n *\n * @example\n * ```tsx\n * import { TruthProvider, usePatients, useAppointments } from '@hipnation-truth/sdk/react';\n *\n * function App() {\n * return (\n * <TruthProvider environment=\"sandbox\">\n * <PatientList />\n * </TruthProvider>\n * );\n * }\n *\n * function PatientList() {\n * const patients = usePatients({ limit: 20 });\n * if (!patients) return <div>Loading...</div>;\n * return patients.map(p => <div key={p._id}>{p.firstName}</div>);\n * }\n * ```\n */\n\n// Dialpad call event hooks (Convex-backed)\nexport type {\n DialpadCallLogRow,\n DialpadCallRow,\n UseActiveCallsOptions,\n} from \"./react/calls\";\nexport {\n ACTIVE_CALL_STATES,\n CONNECTED_CALL_STATES,\n DialpadCallState,\n RINGING_CALL_STATES,\n TERMINAL_CALL_STATES,\n useActiveCalls,\n useDialpadCallByCallId,\n useDialpadCallLog,\n useDialpadCallsForConversation,\n} from \"./react/calls\";\n// Reactive conversation / message hooks (Convex-backed, PR #110 schema)\nexport type {\n ConversationListItem,\n ConversationMessageRow,\n ConversationNoteRow,\n ConversationRow,\n ConversationTaskForUserRow,\n ConversationTaskRow,\n UseConversationsFilters,\n UseMessagesOptions,\n UseQueryResult,\n} from \"./react/conversations\";\nexport {\n useConversationByPhonePair,\n useConversationNotes,\n useConversationNotesByPhonePair,\n useConversations,\n useConversationTasks,\n useConversationTasksByPhonePair,\n useConversationTasksForUser,\n useMessages,\n useUnreadCount,\n} from \"./react/conversations\";\n// Re-export types consumers commonly need\nexport type {\n ConversationMessage,\n Physician,\n UseAppointmentListOptions,\n UseConversationMessagesOptions,\n UsePatientBasicOptions,\n UsePatientBasicResult,\n UsePatientListOptions,\n UsePatientMedicalOptions,\n UsePatientPhotoOptions,\n} from \"./react/hooks\";\n// Patient / Appointment / Physician / Medical / Messages hooks\nexport {\n useAppointment,\n useAppointmentByElationId,\n useAppointments,\n useConversationMessages,\n usePatient,\n usePatientBasic,\n usePatientByElationId,\n usePatientByHintId,\n usePatientMedical,\n usePatientPhoto,\n usePatients,\n usePharmacyByNcpdpId,\n usePhysicianByElationId,\n usePhysiciansByElationIds,\n} from \"./react/hooks\";\n// Notifications\nexport type {\n PermissionStatus,\n UseNotificationsOptions,\n UseNotificationsResult,\n} from \"./react/notifications\";\nexport { useNotifications } from \"./react/notifications\";\n// Patient family members\nexport type {\n FamilyMemberRow,\n UsePatientFamilyMembersInput,\n} from \"./react/patient-family\";\nexport { usePatientFamilyMembers } from \"./react/patient-family\";\n// Patient search (multi-mode, office-scoped)\nexport type {\n PatientSearchResult,\n UsePatientSearchOptions,\n} from \"./react/patient-search\";\nexport { usePatientSearch } from \"./react/patient-search\";\n// Bulk patient lookup\nexport {\n usePatientsByIds,\n usePatientsByPhones,\n} from \"./react/patients-bulk\";\nexport type { TruthProviderProps } from \"./react/provider\";\n// Provider\nexport { TruthProvider } from \"./react/provider\";\n// Reminders\nexport { useRemindersForConversations } from \"./react/reminders\";\nexport type {\n TruthTrackingContextValue,\n TruthTrackingProviderProps,\n} from \"./react/tracking\";\n// Tracking\nexport { TruthTrackingProvider, useTruth } from \"./react/tracking\";\n// User settings\nexport { useUserSettings } from \"./react/user-settings\";\n// Voicemail URL resolver\nexport { useVoicemailUrl } from \"./react/voicemail\";\n// Re-export event types for track() calls\nexport type {\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"./tracking/events\";\nexport type { Appointment, AppointmentListOptions } from \"./types/appointment\";\nexport type { Patient, PatientListOptions } from \"./types/patient\";\n","/**\n * React hooks for Dialpad call events — replaces CommHub's Hasura\n * `useDialpad_EventsSubscription`. Powered by the new\n * `dialpadCallEvents` + `dialpadCallEventLog` Convex tables that the\n * Truth webhook handler writes on every call state transition.\n *\n * Same `{ data, loading, error }` contract as the other hooks in this\n * SDK; same `skip` semantics on missing arguments.\n *\n * @example\n * ```tsx\n * import { useActiveCalls, useDialpadCallByCallId } from '@hipnation-truth/sdk/react';\n *\n * function IncomingBanner() {\n * const { data: active } = useActiveCalls();\n * if (!active?.length) return null;\n * return <Banner calls={active} />;\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n\n// ---------------------------------------------------------------------------\n// Call state — single source of truth, mirrors the Dialpad webhook\n// `state` field and the values written to the Convex `dialpadCallEvents`\n// table by the Truth webhook handler. Consumers can switch on the\n// const-as-enum or compare the row's raw `state` string directly.\n// ---------------------------------------------------------------------------\n\nexport const DialpadCallState = {\n Calling: \"calling\",\n Ringing: \"ringing\",\n Connected: \"connected\",\n Hold: \"hold\",\n Hangup: \"hangup\",\n Missed: \"missed\",\n VoicemailUploaded: \"voicemail_uploaded\",\n} as const;\nexport type DialpadCallState =\n (typeof DialpadCallState)[keyof typeof DialpadCallState];\n\n/** Ringing states — caller hasn't been picked up yet. */\nexport const RINGING_CALL_STATES: ReadonlySet<DialpadCallState> = new Set([\n DialpadCallState.Calling,\n DialpadCallState.Ringing,\n]);\n\n/** Connected states — call is live (or on hold). */\nexport const CONNECTED_CALL_STATES: ReadonlySet<DialpadCallState> = new Set([\n DialpadCallState.Connected,\n]);\n\n/**\n * Active states — anything not terminal. Default filter for\n * `useActiveCalls`; the union of ringing + connected. `hold` is NOT\n * included by default because the IncomingCalls UI doesn't surface\n * held calls in the inbox banner.\n */\nexport const ACTIVE_CALL_STATES: ReadonlySet<DialpadCallState> = new Set([\n ...RINGING_CALL_STATES,\n ...CONNECTED_CALL_STATES,\n]);\n\n/** Terminal states — call has ended. Used to exclude rows from active lists. */\nexport const TERMINAL_CALL_STATES: ReadonlySet<DialpadCallState> = new Set([\n DialpadCallState.Hangup,\n DialpadCallState.Missed,\n DialpadCallState.VoicemailUploaded,\n]);\n\n// ---------------------------------------------------------------------------\n// Row shapes — mirror the Convex `dialpadCallEvents` table on the Truth\n// schema. Once the consuming app regenerates `convex/_generated/api.d.ts`\n// these can be inferred.\n// ---------------------------------------------------------------------------\n\nexport interface DialpadCallRow {\n _id: string;\n _creationTime: number;\n callId: string;\n state: string;\n direction: string | null;\n fromNumber: string | null;\n toNumber: string | null;\n phonePair: string | null;\n conversationId: string | null;\n patientId: string | null;\n voicemailLink: string | null;\n voicemailDurationSec: number | null;\n transcript: unknown | null;\n duration: number | null;\n occurredAt: string;\n updatedAt: string;\n}\n\nexport interface DialpadCallLogRow {\n _id: string;\n _creationTime: number;\n callId: string;\n state: string;\n direction: string | null;\n payload: unknown | null;\n occurredAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// Convex refs\n// ---------------------------------------------------------------------------\n\nconst listActiveRef = makeFunctionReference<\n \"query\",\n { limit?: number; terminalStates?: string[] },\n DialpadCallRow[]\n>(\"dialpadCallEvents:listActive\");\n\nconst listForConversationRef = makeFunctionReference<\n \"query\",\n { conversationId: string; limit?: number },\n DialpadCallRow[]\n>(\"dialpadCallEvents:listForConversation\");\n\nconst getByCallIdRef = makeFunctionReference<\n \"query\",\n { callId: string },\n DialpadCallRow | null\n>(\"dialpadCallEvents:getByCallId\");\n\nconst listLogForCallIdRef = makeFunctionReference<\n \"query\",\n { callId: string; limit?: number },\n DialpadCallLogRow[]\n>(\"dialpadCallEvents:listLogForCallId\");\n\n// ---------------------------------------------------------------------------\n// Helpers — duplicated from conversations.ts to keep this file independent\n// ---------------------------------------------------------------------------\n\nconst SKIP = \"skip\" as const;\n\nfunction toResult<T>(\n value: T | undefined,\n skipped: boolean,\n): UseQueryResult<T> {\n if (skipped) {\n return { data: undefined, loading: false, error: undefined };\n }\n return {\n data: value,\n loading: value === undefined,\n error: undefined,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\nexport interface UseActiveCallsOptions {\n /** Page cap. Default 50. */\n limit?: number;\n /**\n * Terminal states excluded from the active list. Override only if\n * Dialpad introduces a new state.\n */\n terminalStates?: string[];\n}\n\n/**\n * Live list of every Dialpad call that hasn't reached a terminal state\n * — used by CommHub's IncomingCalls banner. Updates as the webhook\n * handler patches the per-call row.\n */\nexport function useActiveCalls(\n options?: UseActiveCallsOptions,\n): UseQueryResult<DialpadCallRow[]> {\n const result = useQuery(\n listActiveRef as FunctionReference<\"query\">,\n options ?? {},\n ) as DialpadCallRow[] | undefined;\n return toResult(result, false);\n}\n\n/**\n * All calls (most-recent-first) on a single conversation. Pass the\n * Convex conversation `_id` (e.g. from `useConversationByPhonePair`).\n */\nexport function useDialpadCallsForConversation(\n conversationId: string | null | undefined,\n options?: { limit?: number },\n): UseQueryResult<DialpadCallRow[]> {\n const skipped = !conversationId;\n const result = useQuery(\n listForConversationRef as FunctionReference<\"query\">,\n skipped\n ? SKIP\n : {\n conversationId: conversationId as string,\n limit: options?.limit,\n },\n ) as DialpadCallRow[] | undefined;\n return toResult(result, skipped);\n}\n\n/** Single call lookup by Dialpad `call_id`. */\nexport function useDialpadCallByCallId(\n callId: string | null | undefined,\n): UseQueryResult<DialpadCallRow | null> {\n const skipped = !callId;\n const result = useQuery(\n getByCallIdRef as FunctionReference<\"query\">,\n skipped ? SKIP : { callId: callId as string },\n ) as DialpadCallRow | null | undefined;\n return toResult(result, skipped);\n}\n\n/**\n * Full audit history of state transitions for a call — backs the\n * hold / recording / transcription timeline in the right panel.\n */\nexport function useDialpadCallLog(\n callId: string | null | undefined,\n options?: { limit?: number },\n): UseQueryResult<DialpadCallLogRow[]> {\n const skipped = !callId;\n const result = useQuery(\n listLogForCallIdRef as FunctionReference<\"query\">,\n skipped ? SKIP : { callId: callId as string, limit: options?.limit },\n ) as DialpadCallLogRow[] | undefined;\n return toResult(result, skipped);\n}\n","/**\n * React hooks for Truth SDK — reactive conversation + message data.\n *\n * Wraps Convex queries owned by the Truth backend so consumers (CommHub\n * Expo frontend) get live-updating conversation lists, message threads,\n * and unread totals without managing subscriptions themselves.\n *\n * **Hook contract:** every hook in this file returns\n * `{ data, loading, error }`. `data` is `undefined` while loading and\n * also when the underlying query is `\"skip\"`'d (caller didn't pass the\n * required arg yet). `loading` is `true` only while a real subscription\n * is in-flight; once `data` resolves (even to `null`) `loading` flips\n * to `false`. `error` is reserved for SDK-side validation — Convex\n * itself throws on subscribe rather than returning an error value, so\n * unhandled query errors propagate as React errors and should be caught\n * with an error boundary.\n *\n * **Backed by PR #110 schema (commit `41dbb59` on\n * `feat/migrate-dialpad-webhook-and-messages-to-truth`):**\n * - `conversations:listForUser` → `useConversations`\n * - `conversations:getUnreadTotalForUser` → `useUnreadCount`\n * - `conversations:getByPhonePair` → `useConversationByPhonePair`\n * - `conversationMessages:getByConversationId` → `useMessages`\n *\n * **Deliberately NOT shipped here (no backend query yet — flagged in\n * PR #111 body for follow-up):**\n * - Single-conversation-by-id lookup. Convex `conversations` has no\n * `get(id)` query — only `getByPhonePair`. CommHub uses phonePair as\n * the primary handle, so the byPair flavor is what consumers need;\n * if id-based lookup is needed later we can add a `conversations:get`\n * in Truth.\n * - Participants / \"seen by\" hook. The migrated schema has no\n * `conversationParticipants` table — read state is per-user via\n * `conversationReads` and there's no public list query for it yet.\n * - `markRead` mutation. PR #110 declares `markRead` as\n * `internalMutation`, so the frontend can't invoke it directly — it\n * has to go through a Truth HTTP endpoint, or webhook-migrator needs\n * to flip it to a public `mutation`. Flagged for a follow-up.\n *\n * Must be used within `<TruthProvider />` (see `./provider`).\n *\n * @example\n * ```tsx\n * import {\n * useConversations,\n * useConversationByPhonePair,\n * useMessages,\n * useUnreadCount,\n * } from '@hipnation-truth/sdk/react';\n *\n * function Inbox({ userId }: { userId: string }) {\n * const { data: convos, loading } = useConversations({ userId });\n * const { data: unread } = useUnreadCount(userId);\n * if (loading) return <Spinner />;\n * return (\n * <div>\n * <Badge count={unread ?? 0} />\n * {convos?.map((c) => <ConvoRow key={c.id} convo={c} />)}\n * </div>\n * );\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n//\n// Mirror the shapes returned by the Convex queries on PR #110. Once\n// the consuming app regenerates `convex/_generated/api.d.ts` against\n// the merged schema these can be replaced with inferred types — for\n// today they give consumers useful autocomplete.\n\n/**\n * Row shape returned by `conversations:listForUser` — joins the base\n * `conversations` row with the caller's per-user `conversationReads`\n * entry so unread state lives on each item.\n */\nexport interface ConversationListItem {\n /** Convex id of the conversation row. */\n id: string;\n patientPhone: string;\n providerPhone: string;\n /** Sorted-digit-pair key (commutative — same regardless of direction). */\n phonePair: string;\n /** Truth-side patient id, when the kinesis pipeline could resolve it. */\n patientId: string | null;\n /** ISO timestamp of the most recent message in the conversation. */\n lastMessageAt: string;\n /** Caller's unread count for this conversation. */\n unreadCount: number;\n /** ISO timestamp of when the caller last marked the conversation read. */\n lastReadAt: string | null;\n}\n\n/**\n * Raw `conversations` table row — what `getByPhonePair` returns. No\n * unread / read joining; for that, prefer `useConversations`.\n */\nexport interface ConversationRow {\n _id: string;\n _creationTime: number;\n patientPhone: string;\n providerPhone: string;\n phonePair: string;\n patientId: string | null;\n lastEventId: string | null;\n lastMessageAt: string;\n createdAt: string;\n}\n\n/**\n * Message row returned by `conversationMessages:getByConversationId` —\n * calls + SMS merged chronologically. Mirrors `ConversationMessage`\n * already exported from the SDK; re-exported here under a clearer name\n * for the new hook surface.\n */\n/**\n * Conversation note row — single entry shown in the right-panel notes\n * tab in CommHub. Replaces the urql Hasura `internal_activities` row\n * with `type='note'`.\n */\nexport interface ConversationNoteRow {\n /** Convex id of the conversationNotes row. */\n id: string;\n conversationId: string;\n /** Dialpad event the note is attached to, when present. */\n eventId: string | null;\n author: string;\n content: string;\n /** Free-form status flag — matches CommHub's existing schema. */\n status: string | null;\n /** Free-form `type` discriminator — matches CommHub's existing schema. */\n type: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Conversation task row — single entry shown in the right-panel tasks\n * tab. Replaces the urql Hasura `internal_activities` row with\n * `type='task'`.\n */\nexport interface ConversationTaskRow {\n id: string;\n conversationId: string;\n eventId: string | null;\n author: string;\n description: string;\n status: \"pending\" | \"completed\";\n priority: \"high\" | \"medium\" | \"low\" | null;\n assignee: string | null;\n resolvedBy: string | null;\n type: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Cross-conversation task row used by the \"My Tasks\" tab. Adds the\n * conversation's normalized phone pair so the UI can render the\n * patient handle without a second reactive query.\n */\nexport interface ConversationTaskForUserRow extends ConversationTaskRow {\n /** Normalized `(patient_phone | provider_phone)` for the task's conversation. */\n phonePair: string | null;\n}\n\nexport interface ConversationMessageRow {\n kind: \"call\" | \"sms\";\n id: string;\n providerId: string;\n state: string | null;\n direction: string | null;\n fromNumber: string | null;\n toNumber: string | null;\n voicemailLink: string | null;\n duration: number | null;\n text: string | null;\n mms: boolean;\n mmsUrl: string | null;\n messageStatus: string | null;\n occurredAt: string;\n conversationId: string | null;\n patientId: string | null;\n}\n\nexport interface UseQueryResult<T> {\n /**\n * Query data. `undefined` while loading or while the query is\n * intentionally skipped (e.g. caller hasn't supplied `userId` yet).\n */\n data: T | undefined;\n /** True only while a real subscription is in-flight. */\n loading: boolean;\n /**\n * Reserved for client-side validation errors surfaced by the SDK\n * itself. Convex query errors propagate as React errors and should\n * be caught with an error boundary.\n */\n error: Error | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Convex function references — string-keyed, decoupled from the\n// consuming app's generated `convex/_generated/api`. Names below match\n// the queries on PR #110 (commit 41dbb59) verbatim.\n// ---------------------------------------------------------------------------\n\nconst conversationsListForUserRef = makeFunctionReference<\n \"query\",\n { userId: string; limit?: number },\n ConversationListItem[]\n>(\"conversations:listForUser\");\n\nconst conversationsSearchForUserRef = makeFunctionReference<\n \"query\",\n { userId: string; search: string; limit?: number },\n ConversationListItem[]\n>(\"conversations:searchForUser\");\n\nconst conversationsGetUnreadTotalForUserRef = makeFunctionReference<\n \"query\",\n { userId: string },\n number\n>(\"conversations:getUnreadTotalForUser\");\n\nconst conversationsGetByPhonePairRef = makeFunctionReference<\n \"query\",\n { phonePair: string },\n ConversationRow | null\n>(\"conversations:getByPhonePair\");\n\n// Lives on the existing `conversationMessages` module (pre-#110). PR\n// #110 added `conversationId` indexing on `messageSms` / `messageCalls`,\n// so this query is now reliable for the new conversations table.\nconst conversationMessagesGetByConversationIdRef = makeFunctionReference<\n \"query\",\n { conversationId: string; limit?: number },\n ConversationMessageRow[]\n>(\"conversationMessages:getByConversationId\");\n\n// Notes + tasks for a conversation thread (Truth #730). Both lists are\n// keyed on the Convex conversation `_id` — same id that comes back\n// from `useConversations`.\nconst conversationNotesListForConversationRef = makeFunctionReference<\n \"query\",\n { conversationId: string },\n ConversationNoteRow[]\n>(\"conversationNotes:listForConversation\");\n\nconst conversationTasksListForConversationRef = makeFunctionReference<\n \"query\",\n { conversationId: string },\n ConversationTaskRow[]\n>(\"conversationTasks:listForConversation\");\n\n// Phone-pair-keyed variants (Truth SDK 0.10.0). CommHub holds the\n// conversation by `(patient_phone, provider_phone)` and shouldn't have\n// to chain `useConversationByPhonePair → useConversationNotes`. These\n// resolve the conversation server-side and return the same row shape.\nconst conversationNotesListByPhonePairRef = makeFunctionReference<\n \"query\",\n { phonePair: string },\n ConversationNoteRow[]\n>(\"conversationNotes:listByPhonePair\");\n\nconst conversationTasksListByPhonePairRef = makeFunctionReference<\n \"query\",\n { phonePair: string },\n ConversationTaskRow[]\n>(\"conversationTasks:listByPhonePair\");\n\nconst conversationTasksListForUserRef = makeFunctionReference<\n \"query\",\n { userId: string; limit?: number },\n ConversationTaskForUserRow[]\n>(\"conversationTasks:listForUser\");\n\n// ---------------------------------------------------------------------------\n// Skipped-query sentinel + result shaping\n// ---------------------------------------------------------------------------\n\nconst SKIP = \"skip\" as const;\n\nfunction toResult<T>(\n value: T | undefined,\n skipped: boolean,\n): UseQueryResult<T> {\n if (skipped) {\n return { data: undefined, loading: false, error: undefined };\n }\n return {\n data: value,\n loading: value === undefined,\n error: undefined,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Filters + options\n// ---------------------------------------------------------------------------\n\nexport interface UseConversationsFilters {\n /**\n * Truth user id (the Better Auth subject). Pass `undefined` to skip\n * the query — useful when the auth session is still loading.\n */\n userId: string | null | undefined;\n /**\n * Optional search term — when non-empty, switches the hook from\n * `conversations:listForUser` to `conversations:searchForUser`, which\n * fans out two parallel Convex full-text patient searches\n * (firstName + lastName) and returns the matching conversations the\n * user can see, sorted by recency. Whitespace-only values are treated\n * as empty.\n */\n search?: string;\n /** Page size. Server caps at 100 by default. */\n limit?: number;\n}\n\nexport interface UseMessagesOptions {\n /** Page size. Server caps at 200 by default. */\n limit?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to a user's conversations — joined with their per-conversation\n * unread count, sorted by most recent message. Updates live as new\n * SMS / calls land in Convex and the webhook bumps `unreadCount`.\n *\n * Returns `{ data: undefined, loading: false }` while the auth session\n * resolves (`userId === undefined`); not an error, just a skip.\n */\nfunction useConversations(\n filters: UseConversationsFilters,\n): UseQueryResult<ConversationListItem[]> {\n const trimmedSearch = filters.search?.trim() ?? \"\";\n const isSearchMode = trimmedSearch.length > 0;\n const skipped = !filters.userId;\n\n const listResult = useQuery(\n conversationsListForUserRef as FunctionReference<\"query\">,\n skipped || isSearchMode\n ? SKIP\n : {\n userId: filters.userId as string,\n limit: filters.limit,\n },\n ) as ConversationListItem[] | undefined;\n\n const searchResult = useQuery(\n conversationsSearchForUserRef as FunctionReference<\"query\">,\n skipped || !isSearchMode\n ? SKIP\n : {\n userId: filters.userId as string,\n search: trimmedSearch,\n limit: filters.limit,\n },\n ) as ConversationListItem[] | undefined;\n\n return toResult(isSearchMode ? searchResult : listResult, skipped);\n}\n\n/**\n * Look up a single conversation by its normalized phonePair (sorted\n * digit-pair, e.g. `\"5125550123|6505551234\"`). Returns `null` if no\n * conversation exists yet for that pair.\n *\n * Use the `phonePairKey(patientPhone, providerPhone)` helper exposed by\n * the Truth Convex module to derive the key from raw phone strings.\n */\nfunction useConversationByPhonePair(\n phonePair: string | null | undefined,\n): UseQueryResult<ConversationRow | null> {\n const skipped = !phonePair;\n const result = useQuery(\n conversationsGetByPhonePairRef as FunctionReference<\"query\">,\n skipped ? SKIP : { phonePair: phonePair as string },\n ) as ConversationRow | null | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Subscribe to a paginated message stream for a single conversation.\n * Calls + SMS merged chronologically, newest-first. Live as the\n * kinesis consumer writes to `messageCalls` / `messageSms`.\n *\n * Pass the Convex `_id` of the conversation row (from\n * `useConversations` / `useConversationByPhonePair`) as\n * `conversationId`.\n */\nfunction useMessages(\n conversationId: string | null | undefined,\n options?: UseMessagesOptions,\n): UseQueryResult<ConversationMessageRow[]> {\n const skipped = !conversationId;\n const result = useQuery(\n conversationMessagesGetByConversationIdRef as FunctionReference<\"query\">,\n skipped\n ? SKIP\n : {\n conversationId: conversationId as string,\n limit: options?.limit,\n },\n ) as ConversationMessageRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Subscribe to the user's total unread message count across all\n * conversations. Backs the inbox tab badge in CommHub.\n *\n * Returns `0` when the underlying query resolves with no unread\n * messages, `undefined` while loading or when `userId` is missing.\n */\nfunction useUnreadCount(\n userId: string | null | undefined,\n): UseQueryResult<number> {\n const skipped = !userId;\n const result = useQuery(\n conversationsGetUnreadTotalForUserRef as FunctionReference<\"query\">,\n skipped ? SKIP : { userId: userId as string },\n ) as number | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Subscribe to all notes attached to a conversation, newest first.\n * Replaces the urql Hasura `useConversation_NotesSubscription` in\n * CommHub (Truth #730). Pass `null`/`undefined` to skip the query.\n */\nfunction useConversationNotes(\n conversationId: string | null | undefined,\n): UseQueryResult<ConversationNoteRow[]> {\n const skipped = !conversationId;\n const result = useQuery(\n conversationNotesListForConversationRef as FunctionReference<\"query\">,\n skipped ? SKIP : { conversationId: conversationId as string },\n ) as ConversationNoteRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Subscribe to all tasks attached to a conversation, newest first.\n * Replaces the urql Hasura `useConversation_TasksSubscription` in\n * CommHub (Truth #730).\n */\nfunction useConversationTasks(\n conversationId: string | null | undefined,\n): UseQueryResult<ConversationTaskRow[]> {\n const skipped = !conversationId;\n const result = useQuery(\n conversationTasksListForConversationRef as FunctionReference<\"query\">,\n skipped ? SKIP : { conversationId: conversationId as string },\n ) as ConversationTaskRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Same as `useConversationNotes` but keyed on the normalized\n * `(patient_phone | provider_phone)` pair — saves the caller from\n * chaining a separate `useConversationByPhonePair` call. Returns\n * `[]` if no conversation exists for the pair yet.\n */\nfunction useConversationNotesByPhonePair(\n phonePair: string | null | undefined,\n): UseQueryResult<ConversationNoteRow[]> {\n const skipped = !phonePair;\n const result = useQuery(\n conversationNotesListByPhonePairRef as FunctionReference<\"query\">,\n skipped ? SKIP : { phonePair: phonePair as string },\n ) as ConversationNoteRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Same as `useConversationTasks` but keyed on phonePair.\n */\n/**\n * Subscribe to every conversation task where the caller is the\n * assignee or the author. Backs CommHub's \"My Tasks\" tab.\n */\nfunction useConversationTasksForUser(\n userId: string | null | undefined,\n options?: { limit?: number },\n): UseQueryResult<ConversationTaskForUserRow[]> {\n const skipped = !userId;\n const result = useQuery(\n conversationTasksListForUserRef as FunctionReference<\"query\">,\n skipped ? SKIP : { userId: userId as string, limit: options?.limit },\n ) as ConversationTaskForUserRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\nfunction useConversationTasksByPhonePair(\n phonePair: string | null | undefined,\n): UseQueryResult<ConversationTaskRow[]> {\n const skipped = !phonePair;\n const result = useQuery(\n conversationTasksListByPhonePairRef as FunctionReference<\"query\">,\n skipped ? SKIP : { phonePair: phonePair as string },\n ) as ConversationTaskRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\nexport {\n useConversationByPhonePair,\n useConversationNotes,\n useConversationNotesByPhonePair,\n useConversations,\n useConversationTasks,\n useConversationTasksByPhonePair,\n useConversationTasksForUser,\n useMessages,\n useUnreadCount,\n};\n","/**\n * React hooks for Truth SDK — real-time Convex-backed data access.\n *\n * These hooks use Convex React subscriptions for live-updating data.\n * Must be used within a ConvexProvider (see TruthProvider).\n *\n * @example\n * ```tsx\n * import { usePatients, useAppointments } from '@hipnation-truth/sdk/react';\n *\n * function PatientList() {\n * const patients = usePatients({ limit: 20 });\n * return patients?.map(p => <div key={p._id}>{p.firstName} {p.lastName}</div>);\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport { useEffect } from \"react\";\n\n// ---------------------------------------------------------------------------\n// Convex function references\n// ---------------------------------------------------------------------------\n// We use string-based references to avoid importing the generated API\n// (which lives in the consuming app, not the SDK). Convex supports this\n// via the `api` module pattern, but for a published SDK we use\n// makeFunctionReference to stay decoupled from the app's codegen.\n\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\n\n// Patient query references\nconst patientsListRef = makeFunctionReference<\n \"query\",\n {\n search?: string;\n lastName?: string;\n cursor?: string;\n limit?: number;\n },\n unknown[]\n>(\"patients:list\");\n\nconst patientsGetRef = makeFunctionReference<\n \"query\",\n { id: string },\n unknown | null\n>(\"patients:get\");\n\nconst patientsByElationIdRef = makeFunctionReference<\n \"query\",\n { elationId: string },\n unknown | null\n>(\"patients:getByElationId\");\n\nconst patientsByHintIdRef = makeFunctionReference<\n \"query\",\n { hintId: string },\n unknown | null\n>(\"patients:getByHintId\");\n\n// Appointment query references\nconst appointmentsListRef = makeFunctionReference<\n \"query\",\n {\n patientId?: string;\n status?: string;\n startDate?: string;\n endDate?: string;\n limit?: number;\n },\n unknown[]\n>(\"appointments:list\");\n\nconst appointmentsGetRef = makeFunctionReference<\n \"query\",\n { id: string },\n unknown | null\n>(\"appointments:get\");\n\nconst appointmentsByElationIdRef = makeFunctionReference<\n \"query\",\n { elationId: string },\n unknown | null\n>(\"appointments:getByElationId\");\n\n// ---------------------------------------------------------------------------\n// Patient hooks\n// ---------------------------------------------------------------------------\n\ninterface UsePatientListOptions {\n search?: string;\n lastName?: string;\n limit?: number;\n}\n\n/**\n * Subscribe to a list of patients with optional search and filtering.\n * Returns undefined while loading, then an array of patients.\n */\nfunction usePatients(options?: UsePatientListOptions) {\n return useQuery(patientsListRef as FunctionReference<\"query\">, options ?? {});\n}\n\n/**\n * Subscribe to a single patient by Convex document ID.\n */\nfunction usePatient(id: string) {\n return useQuery(patientsGetRef as FunctionReference<\"query\">, { id });\n}\n\n/**\n * Subscribe to a patient by their Elation ID.\n */\nfunction usePatientByElationId(elationId: string) {\n return useQuery(patientsByElationIdRef as FunctionReference<\"query\">, {\n elationId,\n });\n}\n\n/**\n * Subscribe to a patient by their Hint ID.\n */\nfunction usePatientByHintId(hintId: string) {\n return useQuery(patientsByHintIdRef as FunctionReference<\"query\">, {\n hintId,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Appointment hooks\n// ---------------------------------------------------------------------------\n\ninterface UseAppointmentListOptions {\n patientId?: string;\n status?: string;\n startDate?: string;\n endDate?: string;\n limit?: number;\n}\n\n/**\n * Subscribe to a list of appointments with optional filtering.\n */\nfunction useAppointments(options?: UseAppointmentListOptions) {\n return useQuery(\n appointmentsListRef as FunctionReference<\"query\">,\n options ?? {},\n );\n}\n\n/**\n * Subscribe to a single appointment by Convex document ID.\n */\nfunction useAppointment(id: string) {\n return useQuery(appointmentsGetRef as FunctionReference<\"query\">, { id });\n}\n\n/**\n * Subscribe to an appointment by its Elation ID.\n */\nfunction useAppointmentByElationId(elationId: string) {\n return useQuery(appointmentsByElationIdRef as FunctionReference<\"query\">, {\n elationId,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Physician hooks\n// ---------------------------------------------------------------------------\n\ninterface Physician {\n _id: string;\n elationId: number;\n firstName?: string;\n lastName?: string;\n npi?: string;\n credentials?: string;\n specialties?: string[];\n practice?: number;\n email?: string;\n phone?: string;\n lastSyncedAt: string;\n}\n\nconst physiciansGetByElationIdsRef = makeFunctionReference<\n \"query\",\n { ids: number[] },\n Physician[]\n>(\"physicians:getByElationIds\");\n\nconst physiciansGetByElationIdRef = makeFunctionReference<\n \"query\",\n { id: number },\n Physician | null\n>(\"physicians:getByElationId\");\n\n/**\n * Resolve a batch of physicians by their Elation IDs. Returns an array\n * of physicians that exist in Convex; missing ids are silently dropped.\n *\n * Use this to resolve medication `prescribing_physician` / appointment\n * physician ids to display names, avoiding the per-physician HTTP\n * round-trip through Truth's Elation proxy.\n *\n * Pass `undefined` (or an empty array) to skip the query — the hook\n * returns `undefined` until you pass a populated list.\n */\nfunction usePhysiciansByElationIds(ids: number[] | undefined) {\n return useQuery(\n physiciansGetByElationIdsRef as FunctionReference<\"query\">,\n ids && ids.length > 0 ? { ids } : \"skip\",\n );\n}\n\n/**\n * Subscribe to a single physician by their Elation ID.\n */\nfunction usePhysicianByElationId(id: number | undefined) {\n return useQuery(\n physiciansGetByElationIdRef as FunctionReference<\"query\">,\n id !== undefined ? { id } : \"skip\",\n );\n}\n\n// ---------------------------------------------------------------------------\n// Patient medical hooks (medications, problems, allergies, appointments)\n// ---------------------------------------------------------------------------\n\nconst medicationsByPatientRef = makeFunctionReference<\n \"query\",\n { elationPatientId: number },\n unknown[]\n>(\"medicalRecords:getMedicationsByElationPatient\");\n\nconst problemsByPatientRef = makeFunctionReference<\n \"query\",\n { elationPatientId: number },\n unknown[]\n>(\"medicalRecords:getProblemsByElationPatient\");\n\nconst allergiesByPatientRef = makeFunctionReference<\n \"query\",\n { elationPatientId: number },\n unknown[]\n>(\"medicalRecords:getAllergiesByElationPatient\");\n\nconst appointmentsByPatientRef = makeFunctionReference<\n \"query\",\n { elationPatientId: number },\n unknown[]\n>(\"medicalRecords:getAppointmentsByElationPatient\");\n\ninterface UsePatientMedicalOptions {\n /**\n * Base URL of the Truth API. Required to trigger the background refresh\n * (e.g. `https://app.truth.communication-hub.com`). If omitted, the hook\n * only reads from Convex and does not refresh.\n */\n apiBaseUrl?: string;\n /** API key used for the refresh call. */\n apiKey?: string;\n /**\n * If true, suppress the background refresh. Useful when you know the\n * data was just refreshed by another component on the page.\n */\n skipRefresh?: boolean;\n}\n\n/**\n * Composite hook that returns a patient's medical records — medications,\n * problems, allergies, appointments — from the Convex cache.\n *\n * On mount (and when `elationId` changes) fires a background refresh\n * against Truth's `/api/patients/medical/refresh` endpoint so stale data\n * is pulled in without blocking render. Returns cached data immediately;\n * Convex subscription updates the UI when refresh completes.\n */\nfunction usePatientMedical(\n elationId: number | undefined,\n options?: UsePatientMedicalOptions,\n) {\n const medications = useQuery(\n medicationsByPatientRef as FunctionReference<\"query\">,\n elationId !== undefined ? { elationPatientId: elationId } : \"skip\",\n ) as unknown[] | undefined;\n\n const problems = useQuery(\n problemsByPatientRef as FunctionReference<\"query\">,\n elationId !== undefined ? { elationPatientId: elationId } : \"skip\",\n ) as unknown[] | undefined;\n\n const allergies = useQuery(\n allergiesByPatientRef as FunctionReference<\"query\">,\n elationId !== undefined ? { elationPatientId: elationId } : \"skip\",\n ) as unknown[] | undefined;\n\n const appointments = useQuery(\n appointmentsByPatientRef as FunctionReference<\"query\">,\n elationId !== undefined ? { elationPatientId: elationId } : \"skip\",\n ) as unknown[] | undefined;\n\n useEffect(() => {\n if (elationId === undefined || options?.skipRefresh) {\n return;\n }\n const apiBaseUrl = options?.apiBaseUrl;\n const apiKey = options?.apiKey;\n if (!apiBaseUrl || !apiKey) {\n return;\n }\n\n const controller = new AbortController();\n void fetch(`${apiBaseUrl}/api/patients/medical/refresh`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify({ elationId }),\n signal: controller.signal,\n }).catch(() => {\n /* background refresh — failures are non-fatal */\n });\n\n return () => controller.abort();\n }, [elationId, options?.apiBaseUrl, options?.apiKey, options?.skipRefresh]);\n\n return { medications, problems, allergies, appointments };\n}\n\n// ---------------------------------------------------------------------------\n// Patient basic hooks (Elation demographics + Hint memberships/account)\n// ---------------------------------------------------------------------------\n//\n// Replaces the CommHub `getPatientBasicDetails` Hasura Action. Reads\n// from Convex-cached rows (populated by the patients-backfill cron +\n// `/api/patients/basic/refresh` on-demand), falling back to an empty\n// state until the background refresh completes.\n\nconst elationPatientByIdRef = makeFunctionReference<\n \"query\",\n { elationId: number },\n unknown | null\n>(\"elationPatients:getByElationId\");\n\nconst hintPatientByIdRef = makeFunctionReference<\n \"query\",\n { hintId: string },\n unknown | null\n>(\"hintPatients:getByHintId\");\n\nconst pharmacyByNcpdpRef = makeFunctionReference<\n \"query\",\n { ncpdpId: string },\n unknown | null\n>(\"elationPharmacies:getByNcpdpId\");\n\nconst patientPhotoByIdRef = makeFunctionReference<\n \"query\",\n { elationPatientId: number },\n unknown | null\n>(\"elationPatientPhotos:getByElationPatientId\");\n\ninterface UsePatientBasicOptions {\n /** Truth API base URL used for the background refresh. */\n apiBaseUrl?: string;\n /** API key for the refresh call. */\n apiKey?: string;\n /** Suppress the background refresh. */\n skipRefresh?: boolean;\n}\n\ninterface UsePatientBasicResult {\n /**\n * Raw Elation patient payload (matches the shape Elation's\n * `/patients/{id}` returns — `first_name`, `last_name`, `phones`, etc).\n * Returns `undefined` while the cache miss is loading, `null` if the\n * patient isn't in Convex yet (first-open-after-backfill).\n */\n elationPatient: Record<string, unknown> | null | undefined;\n /**\n * Raw Hint patient payload (matches the shape Hint's\n * `/provider/patients/{id}` returns — `memberships`, `account`,\n * `phones`, etc).\n */\n hintPatient: Record<string, unknown> | null | undefined;\n /**\n * The full Convex row for Elation (includes `elationId`, `lastSyncedAt`,\n * `photoS3Key`, `preferredPharmacyNcpdpId`, etc). Use this when you\n * need the structured/typed fields rather than the raw Elation payload.\n */\n elationRow: Record<string, unknown> | null | undefined;\n /**\n * The full Convex row for Hint. Similar relationship to `hintPatient`.\n */\n hintRow: Record<string, unknown> | null | undefined;\n /** True while either cache miss is still pending. */\n loading: boolean;\n}\n\n/**\n * Composite hook returning a patient's basic details — Hint demographics\n * + memberships + account, Elation demographics + clinical metadata —\n * from the Convex cache.\n *\n * On mount (and when inputs change) fires a background refresh against\n * `/api/patients/basic/refresh` so stale rows get pulled fresh without\n * blocking render. Returns cached data immediately; Convex subscription\n * updates the UI when refresh completes.\n */\nfunction usePatientBasic(\n input: { hintId?: string; elationId?: number },\n options?: UsePatientBasicOptions,\n): UsePatientBasicResult {\n const elationRow = useQuery(\n elationPatientByIdRef as FunctionReference<\"query\">,\n input.elationId !== undefined ? { elationId: input.elationId } : \"skip\",\n ) as Record<string, unknown> | null | undefined;\n\n const hintRow = useQuery(\n hintPatientByIdRef as FunctionReference<\"query\">,\n input.hintId !== undefined ? { hintId: input.hintId } : \"skip\",\n ) as Record<string, unknown> | null | undefined;\n\n useEffect(() => {\n if (options?.skipRefresh) {\n return;\n }\n if (!input.hintId && input.elationId === undefined) {\n return;\n }\n const apiBaseUrl = options?.apiBaseUrl;\n const apiKey = options?.apiKey;\n if (!apiBaseUrl || !apiKey) {\n return;\n }\n\n const controller = new AbortController();\n void fetch(`${apiBaseUrl}/api/patients/basic/refresh`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify({\n hintId: input.hintId,\n elationId: input.elationId,\n }),\n signal: controller.signal,\n }).catch(() => {\n /* background refresh — failures are non-fatal */\n });\n\n return () => controller.abort();\n }, [\n input.hintId,\n input.elationId,\n options?.apiBaseUrl,\n options?.apiKey,\n options?.skipRefresh,\n ]);\n\n // Surface the raw payload under elationPatient/hintPatient so consumers\n // can keep using the exact Elation/Hint field names (first_name,\n // memberships, account.past_due_in_cents) with zero mapping.\n const elationPatient =\n elationRow === undefined\n ? undefined\n : elationRow === null\n ? null\n : ((elationRow.raw as Record<string, unknown>) ?? null);\n const hintPatient =\n hintRow === undefined\n ? undefined\n : hintRow === null\n ? null\n : ((hintRow.raw as Record<string, unknown>) ?? null);\n\n const elationLoading =\n input.elationId !== undefined && elationRow === undefined;\n const hintLoading = input.hintId !== undefined && hintRow === undefined;\n\n return {\n elationPatient,\n hintPatient,\n elationRow,\n hintRow,\n loading: elationLoading || hintLoading,\n };\n}\n\n/**\n * Read a shared pharmacy row by NCPDP id. Backs the pharmacy card in\n * the patient panel (from Elation's preferred_pharmacy reference).\n */\nfunction usePharmacyByNcpdpId(ncpdpId: string | undefined) {\n return useQuery(\n pharmacyByNcpdpRef as FunctionReference<\"query\">,\n ncpdpId ? { ncpdpId } : \"skip\",\n );\n}\n\ninterface UsePatientPhotoOptions {\n apiBaseUrl?: string;\n apiKey?: string;\n skipRefresh?: boolean;\n}\n\n/**\n * Subscribe to a patient's profile photo (s3Key + metadata). The\n * consumer constructs a download URL via the Truth attachments resource\n * (signed S3 URL). Fires a background refresh to pull the latest photo\n * binary from Elation + upload to S3.\n */\nfunction usePatientPhoto(\n elationId: number | undefined,\n options?: UsePatientPhotoOptions,\n) {\n const photo = useQuery(\n patientPhotoByIdRef as FunctionReference<\"query\">,\n elationId !== undefined ? { elationPatientId: elationId } : \"skip\",\n );\n\n useEffect(() => {\n if (options?.skipRefresh) {\n return;\n }\n if (elationId === undefined) {\n return;\n }\n const apiBaseUrl = options?.apiBaseUrl;\n const apiKey = options?.apiKey;\n if (!apiBaseUrl || !apiKey) {\n return;\n }\n\n const controller = new AbortController();\n void fetch(`${apiBaseUrl}/api/patients/photo/refresh`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify({ elationId }),\n signal: controller.signal,\n }).catch(() => {\n /* non-fatal */\n });\n\n return () => controller.abort();\n }, [elationId, options?.apiBaseUrl, options?.apiKey, options?.skipRefresh]);\n\n return photo;\n}\n\n// ---------------------------------------------------------------------------\n// Conversation messages (Dialpad calls + SMS merged)\n// ---------------------------------------------------------------------------\n\ninterface ConversationMessage {\n kind: \"call\" | \"sms\";\n id: string;\n providerId: string;\n state: string | null;\n direction: string | null;\n fromNumber: string | null;\n toNumber: string | null;\n voicemailLink: string | null;\n duration: number | null;\n text: string | null;\n mms: boolean;\n mmsUrl: string | null;\n messageStatus: string | null;\n occurredAt: string;\n conversationId: string | null;\n patientId: string | null;\n}\n\nconst messagesByPhonesRef = makeFunctionReference<\n \"query\",\n { phoneA: string; phoneB: string; limit?: number },\n ConversationMessage[]\n>(\"conversationMessages:getByPhones\");\n\nconst messagesByConversationIdRef = makeFunctionReference<\n \"query\",\n { conversationId: string; limit?: number },\n ConversationMessage[]\n>(\"conversationMessages:getByConversationId\");\n\ninterface UseConversationMessagesOptions {\n /** Max items to return (default 200). */\n limit?: number;\n}\n\n/**\n * Subscribe to a conversation's calls + SMS merged chronologically.\n * Pass the patient phone + the provider phone — Truth computes a\n * normalized pair key server-side so formatting differences don't\n * matter.\n *\n * Returns `undefined` while loading, then `ConversationMessage[]`\n * sorted newest-first. Updates live as new webhook events land in\n * Convex via the kinesis consumer.\n */\nfunction useConversationMessages(\n input: { phoneA?: string; phoneB?: string; conversationId?: string },\n options?: UseConversationMessagesOptions,\n): ConversationMessage[] | undefined {\n const hasPair = !!input.phoneA && !!input.phoneB;\n const byPair = useQuery(\n messagesByPhonesRef as FunctionReference<\"query\">,\n hasPair\n ? {\n phoneA: input.phoneA as string,\n phoneB: input.phoneB as string,\n limit: options?.limit,\n }\n : \"skip\",\n ) as ConversationMessage[] | undefined;\n\n const byConvo = useQuery(\n messagesByConversationIdRef as FunctionReference<\"query\">,\n !hasPair && input.conversationId\n ? { conversationId: input.conversationId, limit: options?.limit }\n : \"skip\",\n ) as ConversationMessage[] | undefined;\n\n return hasPair ? byPair : byConvo;\n}\n\nexport {\n usePatients,\n usePatient,\n usePatientByElationId,\n usePatientByHintId,\n useAppointments,\n useAppointment,\n useAppointmentByElationId,\n usePhysiciansByElationIds,\n usePhysicianByElationId,\n usePatientMedical,\n usePatientBasic,\n usePharmacyByNcpdpId,\n usePatientPhoto,\n useConversationMessages,\n};\nexport type { ConversationMessage, UseConversationMessagesOptions };\nexport type {\n UsePatientListOptions,\n UseAppointmentListOptions,\n UsePatientMedicalOptions,\n UsePatientBasicOptions,\n UsePatientBasicResult,\n UsePatientPhotoOptions,\n Physician,\n};\n","/**\n * useNotifications — Truth SDK React hook for push notifications.\n *\n * Shape-compatible with `expo-notifications` where practical so\n * consumers port with minimal change. The hook dynamically imports\n * `expo-notifications` so the SDK doesn't hard-depend on Expo —\n * consumers running on a non-Expo runtime (e.g. a server test harness)\n * can still import the SDK without Metro blowing up.\n *\n * Exposes:\n * - permissionStatus — \"granted\" / \"denied\" / \"undetermined\"\n * - devicePushToken — native APNs/FCM token (undefined until register)\n * - register() — request permission, fetch token, register with Truth\n * - unregister() — revoke the device\n * - addReceivedListener / addResponseListener — re-exports of expo's\n * - getBadgeCount / setBadgeCount — re-exports of expo's\n *\n * Web push (VAPID subscription) lands in Phase 3.\n */\n\n\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n isWebPushSupported,\n registerServiceWorker,\n subscribeToPush,\n subscriptionToJSON,\n} from \"../web-push\";\n\n// `expo-notifications` is an optional peer dep. We lazy-import it via\n// a string-indirection so TypeScript + tsup don't try to resolve its\n// types at build time (the SDK ships without a hard dep so non-Expo\n// consumers — web, Node services — can still import from @hipnation-truth/sdk/react).\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ExpoNotificationsModule = any;\n\nasync function loadExpo(): Promise<ExpoNotificationsModule | null> {\n // Hermes rejects dynamic `import()`, so use Metro's `require()` which\n // is provided in every React Native module's scope. `expo-notifications`\n // is an optional peer dep — non-Expo consumers (web, Node) hit the\n // catch and we return null. tsup keeps this as a literal `require()`\n // (rather than its `__require` shim) because expo-notifications is\n // listed in `external` in tsup.config.ts — Metro's static analyzer\n // needs the literal call site to bundle the module.\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n return require(\"expo-notifications\") as ExpoNotificationsModule;\n } catch {\n return null;\n }\n}\n\nexport type PermissionStatus =\n | \"granted\"\n | \"denied\"\n | \"undetermined\"\n | \"unknown\";\n\nexport interface UseNotificationsOptions {\n /** Truth API base URL, e.g. https://app.truth.communication-hub.com */\n apiBaseUrl: string;\n /** `hn_live_*` API key for the caller's application. */\n apiKey: string;\n /** Current user id — used when registering the device. */\n userId: string | null | undefined;\n /** Optional app version string stored on the device row. */\n appVersion?: string;\n /**\n * Auto-register on mount when permission is already granted.\n * Default: true. Set false if you want to control the registration\n * lifecycle yourself.\n */\n autoRegister?: boolean;\n /** VAPID public key for web push. Fetched automatically if omitted. */\n vapidPublicKey?: string;\n /** Path to the service worker file. Default: \"/truth-sw.js\" */\n serviceWorkerPath?: string;\n /**\n * iOS only — which APNs endpoint the device's token will be valid\n * for. Determined by the build's `aps-environment` entitlement\n * (development ⇒ sandbox, production ⇒ production). Detect at the\n * call site via\n * `Application.getIosPushNotificationServiceEnvironmentAsync()`\n * from `expo-application` and pass the normalised value here. The\n * SDK can't read entitlements itself without taking on a native\n * peer dep. Server falls back to the app-level default if omitted.\n */\n apnsEnvironment?: \"sandbox\" | \"production\";\n}\n\nexport interface UseNotificationsResult {\n permissionStatus: PermissionStatus;\n devicePushToken: string | null;\n register: () => Promise<{ ok: boolean; reason?: string }>;\n unregister: () => Promise<void>;\n addReceivedListener: (listener: (n: unknown) => void) => () => void;\n addResponseListener: (listener: (r: unknown) => void) => () => void;\n getBadgeCount: () => Promise<number>;\n setBadgeCount: (count: number) => Promise<void>;\n}\n\nexport function useNotifications(\n options: UseNotificationsOptions,\n): UseNotificationsResult {\n const [permissionStatus, setPermissionStatus] =\n useState<PermissionStatus>(\"unknown\");\n const [devicePushToken, setDevicePushToken] = useState<string | null>(null);\n const expoRef = useRef<ExpoNotificationsModule | null>(null);\n const isWebRef = useRef(false);\n const vapidKeyRef = useRef<string | null>(options.vapidPublicKey ?? null);\n\n useEffect(() => {\n let mounted = true;\n void (async () => {\n const expo = await loadExpo();\n if (!mounted) {\n return;\n }\n expoRef.current = expo;\n\n if (expo) {\n try {\n const perm = await expo.getPermissionsAsync();\n if (!mounted) {\n return;\n }\n setPermissionStatus(mapStatus(perm?.status));\n } catch {\n setPermissionStatus(\"unknown\");\n }\n return;\n }\n\n if (isWebPushSupported()) {\n isWebRef.current = true;\n if (!vapidKeyRef.current) {\n try {\n const res = await fetch(\n `${options.apiBaseUrl}/api/notifications/vapid-key`,\n {\n headers: {\n Accept: \"application/json\",\n \"X-API-Key\": options.apiKey,\n },\n },\n );\n if (res.ok) {\n const data = await res.json();\n vapidKeyRef.current = data.vapidPublicKey ?? null;\n }\n } catch {\n // best-effort\n }\n }\n if (!mounted) {\n return;\n }\n const webPerm =\n typeof Notification !== \"undefined\"\n ? Notification.permission\n : \"default\";\n setPermissionStatus(\n webPerm === \"granted\"\n ? \"granted\"\n : webPerm === \"denied\"\n ? \"denied\"\n : \"undetermined\",\n );\n } else {\n setPermissionStatus(\"unknown\");\n }\n })();\n return () => {\n mounted = false;\n };\n }, [options.apiBaseUrl, options.apiKey]);\n\n const register = useCallback(async () => {\n if (!options.userId) {\n return { ok: false, reason: \"missing_userId\" };\n }\n\n // Web push path\n if (isWebRef.current) {\n const vapidKey = vapidKeyRef.current;\n if (!vapidKey) {\n return { ok: false, reason: \"no_vapid_key\" };\n }\n\n const webPerm = await Notification.requestPermission();\n setPermissionStatus(\n webPerm === \"granted\"\n ? \"granted\"\n : webPerm === \"denied\"\n ? \"denied\"\n : \"undetermined\",\n );\n if (webPerm !== \"granted\") {\n return { ok: false, reason: \"permission_denied\" };\n }\n\n try {\n const swPath = options.serviceWorkerPath ?? \"/truth-sw.js\";\n const registration = await registerServiceWorker(swPath);\n await navigator.serviceWorker.ready;\n const subscription = await subscribeToPush(registration, vapidKey);\n const subJSON = subscriptionToJSON(subscription);\n setDevicePushToken(subscription.endpoint);\n\n const res = await fetch(\n `${options.apiBaseUrl}/api/notifications/devices/register`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": options.apiKey,\n },\n body: JSON.stringify({\n userId: options.userId,\n platform: \"web\",\n webPushSubscription: subJSON,\n appVersion: options.appVersion,\n locale: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n }),\n },\n );\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n return {\n ok: false,\n reason: `register_failed_${res.status}: ${text.slice(0, 120)}`,\n };\n }\n return { ok: true };\n } catch (err) {\n return {\n ok: false,\n reason: `web_push_error: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n\n // Native (expo-notifications) path\n const expo = expoRef.current ?? (await loadExpo());\n expoRef.current = expo;\n if (!expo) {\n return { ok: false, reason: \"expo_notifications_missing\" };\n }\n\n let perm = await expo.getPermissionsAsync();\n if (perm?.status !== \"granted\") {\n perm = await expo.requestPermissionsAsync();\n }\n setPermissionStatus(mapStatus(perm?.status));\n if (perm?.status !== \"granted\") {\n return { ok: false, reason: \"permission_denied\" };\n }\n\n const tokenResp = await expo.getDevicePushTokenAsync();\n const nativeToken = tokenResp?.data;\n const platform = detectPlatform(tokenResp?.type);\n if (!nativeToken || (platform !== \"ios\" && platform !== \"android\")) {\n return { ok: false, reason: \"no_native_token\" };\n }\n\n setDevicePushToken(nativeToken);\n\n const res = await fetch(\n `${options.apiBaseUrl}/api/notifications/devices/register`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": options.apiKey,\n },\n body: JSON.stringify({\n userId: options.userId,\n platform,\n nativeToken,\n appVersion: options.appVersion,\n locale:\n typeof navigator !== \"undefined\" ? navigator.language : undefined,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n ...(platform === \"ios\" && options.apnsEnvironment\n ? { apnsEnvironment: options.apnsEnvironment }\n : {}),\n }),\n },\n );\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n return {\n ok: false,\n reason: `register_failed_${res.status}: ${text.slice(0, 120)}`,\n };\n }\n return { ok: true };\n }, [\n options.apiBaseUrl,\n options.apiKey,\n options.userId,\n options.appVersion,\n options.serviceWorkerPath,\n ]);\n\n const unregister = useCallback(async () => {\n if (!devicePushToken) {\n return;\n }\n await fetch(`${options.apiBaseUrl}/api/notifications/devices/unregister`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": options.apiKey,\n },\n body: JSON.stringify({ nativeToken: devicePushToken }),\n }).catch(() => {\n /* non-fatal */\n });\n setDevicePushToken(null);\n }, [options.apiBaseUrl, options.apiKey, devicePushToken]);\n\n const addReceivedListener = useCallback(\n (listener: (n: unknown) => void): (() => void) => {\n if (isWebRef.current) {\n if (\n typeof navigator === \"undefined\" ||\n !(\"serviceWorker\" in navigator)\n ) {\n return () => {};\n }\n const handler = (event: MessageEvent) => {\n if (event.data?.type === \"TRUTH_PUSH_RECEIVED\") {\n listener(event.data.payload);\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n return () =>\n navigator.serviceWorker.removeEventListener(\"message\", handler);\n }\n const expo = expoRef.current;\n if (!expo?.addNotificationReceivedListener) {\n return () => {};\n }\n const sub = expo.addNotificationReceivedListener(listener);\n return () => sub.remove?.();\n },\n [],\n );\n\n const addResponseListener = useCallback(\n (listener: (r: unknown) => void): (() => void) => {\n if (isWebRef.current) {\n if (\n typeof navigator === \"undefined\" ||\n !(\"serviceWorker\" in navigator)\n ) {\n return () => {};\n }\n const handler = (event: MessageEvent) => {\n if (event.data?.type === \"TRUTH_PUSH_TAPPED\") {\n listener(event.data.payload);\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n return () =>\n navigator.serviceWorker.removeEventListener(\"message\", handler);\n }\n const expo = expoRef.current;\n if (!expo?.addNotificationResponseReceivedListener) {\n return () => {};\n }\n const sub = expo.addNotificationResponseReceivedListener(listener);\n return () => sub.remove?.();\n },\n [],\n );\n\n const getBadgeCount = useCallback(async (): Promise<number> => {\n const expo = expoRef.current;\n if (!expo?.getBadgeCountAsync) {\n return 0;\n }\n return (await expo.getBadgeCountAsync()) ?? 0;\n }, []);\n\n const setBadgeCount = useCallback(async (count: number): Promise<void> => {\n const expo = expoRef.current;\n if (!expo?.setBadgeCountAsync) {\n return;\n }\n await expo.setBadgeCountAsync(count);\n }, []);\n\n // Auto-register once on mount when conditions are met\n const autoRegister = options.autoRegister !== false;\n useEffect(() => {\n if (!autoRegister) {\n return;\n }\n if (permissionStatus !== \"granted\") {\n return;\n }\n if (devicePushToken) {\n return;\n }\n if (!options.userId) {\n return;\n }\n void register();\n }, [\n autoRegister,\n permissionStatus,\n devicePushToken,\n options.userId,\n register,\n ]);\n\n return {\n permissionStatus,\n devicePushToken,\n register,\n unregister,\n addReceivedListener,\n addResponseListener,\n getBadgeCount,\n setBadgeCount,\n };\n}\n\nfunction mapStatus(status: string | undefined): PermissionStatus {\n if (status === \"granted\") {\n return \"granted\";\n }\n if (status === \"denied\") {\n return \"denied\";\n }\n if (status === \"undetermined\") {\n return \"undetermined\";\n }\n return \"unknown\";\n}\n\nfunction detectPlatform(\n tokenType: string | undefined,\n): \"ios\" | \"android\" | \"web\" | \"unknown\" {\n // expo-notifications >= 0.27 returns \"ios\" / \"android\" / \"web\"\n // directly. Older releases (and Firebase docs) used \"apns\" / \"fcm\";\n // accept both for back-compat with apps still on old SDKs.\n if (tokenType === \"ios\" || tokenType === \"apns\") {\n return \"ios\";\n }\n if (tokenType === \"android\" || tokenType === \"fcm\") {\n return \"android\";\n }\n if (tokenType === \"web\") {\n return \"web\";\n }\n return \"unknown\";\n}\n","/**\n * Web Push helpers for browser environments.\n *\n * Handles service worker registration, VAPID push subscription, and\n * message forwarding from the service worker to the main thread.\n */\n\nexport interface WebPushConfig {\n vapidPublicKey: string;\n serviceWorkerPath?: string;\n}\n\nexport function isWebPushSupported(): boolean {\n return (\n typeof window !== \"undefined\" &&\n \"serviceWorker\" in navigator &&\n \"PushManager\" in window\n );\n}\n\nexport async function registerServiceWorker(\n path = \"/truth-sw.js\",\n): Promise<ServiceWorkerRegistration> {\n return navigator.serviceWorker.register(path);\n}\n\nexport async function subscribeToPush(\n registration: ServiceWorkerRegistration,\n vapidPublicKey: string,\n): Promise<PushSubscription> {\n const existing = await registration.pushManager.getSubscription();\n if (existing) {\n return existing;\n }\n\n return registration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: urlBase64ToUint8Array(\n vapidPublicKey,\n ) as unknown as ArrayBuffer,\n });\n}\n\nexport function subscriptionToJSON(sub: PushSubscription): {\n endpoint: string;\n keys: { p256dh: string; auth: string };\n} {\n const json = sub.toJSON();\n return {\n endpoint: sub.endpoint,\n keys: {\n p256dh: json.keys?.p256dh ?? \"\",\n auth: json.keys?.auth ?? \"\",\n },\n };\n}\n\nfunction urlBase64ToUint8Array(base64String: string): Uint8Array {\n const padding = \"=\".repeat((4 - (base64String.length % 4)) % 4);\n const base64 = (base64String + padding).replace(/-/g, \"+\").replace(/_/g, \"/\");\n const rawData = atob(base64);\n const outputArray = new Uint8Array(rawData.length);\n for (let i = 0; i < rawData.length; ++i) {\n outputArray[i] = rawData.charCodeAt(i);\n }\n return outputArray;\n}\n\nexport function onServiceWorkerMessage(\n type: string,\n callback: (payload: unknown) => void,\n): () => void {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return () => {};\n }\n const handler = (event: MessageEvent) => {\n if (event.data?.type === type) {\n callback(event.data.payload);\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n return () => navigator.serviceWorker.removeEventListener(\"message\", handler);\n}\n","/**\n * React hook for patient family-member lookup — Truth SDK.\n *\n * Replaces the CommHub Hasura `useFamilyMembersQuery` (backed by\n * `queries/family_members.graphql`). Queries the Convex\n * `patients:listFamilyMembers` function, which returns the set of\n * patients that share a `familyId` OR share at least one phone number\n * with the reference patient, excluding the reference patient themselves.\n *\n * Arguments mirror the Hasura `FamilyMembers` GraphQL query variables:\n * - `familyId` → `$familyId` (Hint family/account id)\n * - `phoneNumbers` → `$phoneNumbers` (patient's phone numbers)\n * - `excludeHintId` → `$excludePatientId` (skip the reference patient)\n *\n * Must be used within a `<TruthProvider />`.\n *\n * @example\n * ```tsx\n * import { usePatientFamilyMembers } from '@hipnation-truth/sdk/react/patient-family';\n *\n * function FamilyPanel({ hintId, familyId, phones }: Props) {\n * const { data: members, loading } = usePatientFamilyMembers({\n * familyId,\n * phoneNumbers: phones,\n * excludeHintId: hintId,\n * });\n * if (loading) return <Spinner />;\n * return members?.map((m) => <div key={m._id}>{m.firstName} {m.lastName}</div>);\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Shape of a single patient row returned by `patients:listFamilyMembers`.\n * Mirrors the fields CommHub's `FamilyMembersQuery` selected from Hasura:\n * - `id` → `_id` (Convex document id)\n * - `name` → `firstName + \" \" + lastName`\n * - `hint_id` → `hintId`\n * - `elation_id` → `elationId`\n * - `family_id` → `familyId`\n * - `phone_numbers` → `phones[].number`\n */\nexport interface FamilyMemberRow {\n _id: string;\n _creationTime: number;\n firstName: string;\n lastName: string;\n elationId?: string;\n hintId?: string;\n familyId?: string;\n phones: Array<{ type?: string; number: string }>;\n membershipStatus?: string;\n sources: string[];\n lastSyncedAt: string;\n}\n\n/** Arguments for `usePatientFamilyMembers`. */\nexport interface UsePatientFamilyMembersInput {\n /**\n * Hint family/account id — when present, all patients sharing this id\n * are returned (matches the Hasura `family_id` column).\n *\n * BACKFILL NEEDED: `familyId` was added to the Convex `patients` schema\n * in this PR. Existing patient rows will return empty for this branch\n * until the Hint patient upsert path (upsertFromHint / basic-refresh)\n * is updated to write this field, or a one-time backfill script runs.\n * The phone-number fallback continues to work in the interim.\n */\n familyId?: string | null;\n /**\n * Patient's phone numbers — patients sharing at least one phone number\n * are included as family members (mirrors Hasura `phone_numbers._overlap`).\n */\n phoneNumbers?: string[] | null;\n /**\n * Hint patient ID of the reference patient to exclude from results\n * (mirrors Hasura `$excludePatientId`).\n */\n excludeHintId?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Convex function reference\n// ---------------------------------------------------------------------------\n\nconst patientsFamilyMembersRef = makeFunctionReference<\n \"query\",\n {\n familyId?: string;\n phoneNumbers?: string[];\n excludeHintId?: string;\n },\n FamilyMemberRow[]\n>(\"patients:listFamilyMembers\");\n\n// ---------------------------------------------------------------------------\n// Skip sentinel\n// ---------------------------------------------------------------------------\n\nconst SKIP = \"skip\" as const;\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to family members of a patient in real time.\n *\n * Returns all patients that share the same `familyId` OR share at least one\n * phone number with the reference patient, sorted by name. The reference\n * patient is excluded via `excludeHintId`.\n *\n * Pass `undefined` (or an object where all fields are undefined/null) to\n * skip the query — returns `{ data: undefined, loading: false }`.\n *\n * @param input - Query inputs. The query is skipped if `input` is undefined\n * or all fields are falsy.\n */\nfunction usePatientFamilyMembers(\n input: UsePatientFamilyMembersInput | undefined,\n): UseQueryResult<FamilyMemberRow[]> {\n const hasFamilyId = !!input?.familyId;\n const hasPhoneNumbers = !!(input?.phoneNumbers && input.phoneNumbers.length > 0);\n const shouldQuery = hasFamilyId || hasPhoneNumbers;\n\n const args = shouldQuery\n ? {\n ...(input?.familyId ? { familyId: input.familyId } : {}),\n ...(input?.phoneNumbers && input.phoneNumbers.length > 0\n ? { phoneNumbers: input.phoneNumbers }\n : {}),\n ...(input?.excludeHintId ? { excludeHintId: input.excludeHintId } : {}),\n }\n : SKIP;\n\n const result = useQuery(\n patientsFamilyMembersRef as FunctionReference<\"query\">,\n args,\n ) as FamilyMemberRow[] | undefined;\n\n if (!shouldQuery) {\n return { data: undefined, loading: false, error: undefined };\n }\n\n return {\n data: result,\n loading: result === undefined,\n error: undefined,\n };\n}\n\nexport { usePatientFamilyMembers };\n","/**\n * React hook for Truth SDK — reactive patient search.\n *\n * Wraps the `patients:search` Convex query so CommHub consumers\n * (and other frontends) can search patients by name, phone, or email\n * with live-updating results and optional office scoping, without\n * managing Convex subscriptions themselves.\n *\n * **Hook contract:** returns `UseQueryResult<PatientSearchResult[]>`.\n * - `data` is `undefined` while loading or when the query is skipped\n * (empty `query` string or `query === undefined`).\n * - `loading` is `true` only while a real subscription is in-flight.\n * - `error` is reserved for SDK-side validation; Convex query errors\n * propagate as React errors and should be caught with an error\n * boundary.\n *\n * **Backed by `patients:search`** — added in agent-A-search.\n * Multi-word queries split on whitespace; every token must match at\n * least one of firstName / lastName / email / phone digits.\n *\n * Must be used within `<TruthProvider />` (see `./provider`).\n *\n * @example\n * ```tsx\n * import { usePatientSearch } from '@hipnation-truth/sdk/react/patient-search';\n *\n * function PatientPicker({ officeId }: { officeId?: string }) {\n * const [query, setQuery] = useState('');\n * const { data: patients, loading } = usePatientSearch({ query, officeId });\n * return (\n * <>\n * <input value={query} onChange={e => setQuery(e.target.value)} />\n * {loading && <Spinner />}\n * {patients?.map(p => <PatientRow key={p.id} patient={p} />)}\n * </>\n * );\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n\n// ---------------------------------------------------------------------------\n// Return shape\n// ---------------------------------------------------------------------------\n\n/**\n * A single patient result from `patients:search`.\n *\n * Fields are normalised across the `patients` + `hintPatients` tables\n * so callers get a flat, consistent object regardless of which source\n * contributed the match.\n */\nexport interface PatientSearchResult {\n /** Convex `_id` of the matching `patients` or `hintPatients` row. */\n id: string;\n /** Hint patient id — present when the row originates from Hint. */\n hintId: string | undefined;\n firstName: string;\n lastName: string;\n /** ISO date string (YYYY-MM-DD) — undefined when not available. */\n dob: string | undefined;\n /** Email addresses associated with the patient. */\n emails: string[];\n /**\n * Raw phone strings from the source table. Callers should\n * strip non-digits for dialling; the first entry is the primary\n * contact number.\n */\n phones: string[];\n /** Hint office id — present for Hint-sourced results. */\n officeId: string | undefined;\n /** Human-readable office name — present when officeId is set. */\n officeName: string | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface UsePatientSearchOptions {\n /**\n * The search term entered by the user. Multi-word queries are split\n * on whitespace and every token must match. Pass `\"\"` or `undefined`\n * to skip the query and get `{ data: undefined, loading: false }`.\n */\n query: string | undefined;\n /**\n * When supplied, restricts results to patients whose authoritative\n * Hint office id matches. Maps to the `officeId` arg on the Convex\n * query and uses the `hintPatients.by_officeId` index path.\n */\n officeId?: string | null;\n /**\n * `\"fuzzy\"` (default) uses Convex full-text search indexes for name\n * queries; `\"exact\"` uses a bounded scan-filter only. Most callers\n * should leave this at the default.\n */\n mode?: \"fuzzy\" | \"exact\";\n /** Maximum number of results to return (server caps at 200). */\n limit?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Convex function reference\n// ---------------------------------------------------------------------------\n\n// String-keyed reference to `patients:search` — decoupled from the\n// consuming app's generated `convex/_generated/api`.\nconst patientsSearchRef = makeFunctionReference<\n \"query\",\n {\n query: string;\n officeId?: string;\n limit?: number;\n mode?: \"fuzzy\" | \"exact\";\n },\n PatientSearchResult[]\n>(\"patients:search\");\n\n// ---------------------------------------------------------------------------\n// Skipped-query sentinel\n// ---------------------------------------------------------------------------\n\nconst SKIP = \"skip\" as const;\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to a live patient search backed by `patients:search` on the\n * Truth Convex deployment.\n *\n * Results update reactively whenever the underlying `patients` or\n * `hintPatients` tables change — no polling required.\n *\n * @param options - Search options (see `UsePatientSearchOptions`).\n * @returns `UseQueryResult<PatientSearchResult[]>` — always defined\n * once a non-empty `query` is provided and the subscription resolves.\n */\nexport function usePatientSearch(\n options: UsePatientSearchOptions,\n): UseQueryResult<PatientSearchResult[]> {\n const trimmedQuery = (options.query ?? \"\").trim();\n const skipped = trimmedQuery.length === 0;\n\n const result = useQuery(\n patientsSearchRef as FunctionReference<\"query\">,\n skipped\n ? SKIP\n : {\n query: trimmedQuery,\n ...(options.officeId ? { officeId: options.officeId } : {}),\n ...(options.mode ? { mode: options.mode } : {}),\n ...(options.limit !== undefined ? { limit: options.limit } : {}),\n },\n ) as PatientSearchResult[] | undefined;\n\n if (skipped) {\n return { data: undefined, loading: false, error: undefined };\n }\n\n return {\n data: result,\n loading: result === undefined,\n error: undefined,\n };\n}\n","/**\n * Bulk patient lookup by Convex ids.\n *\n * Backs the inbox card render in CommHub — one reactive subscription\n * resolves names + office + EHR ids for the entire visible list\n * instead of N per-row queries.\n *\n * Must be used within `<TruthProvider />`.\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport { useMemo } from \"react\";\nimport type { Patient } from \"../types/patient\";\nimport type { UseQueryResult } from \"./conversations\";\n\nconst patientsGetByIdsRef = makeFunctionReference<\n \"query\",\n { ids: string[] },\n Patient[]\n>(\"patients:getByIds\");\n\nconst patientsGetByPhonesRef = makeFunctionReference<\n \"query\",\n { phoneDigits: string[] },\n Array<{ phoneDigits: string; patient: Patient }>\n>(\"patients:getByPhones\");\n\nconst SKIP = \"skip\" as const;\n\n/**\n * Subscribe to a list of patients by their Convex ids. Returns a\n * `Record<patientId, Patient>` for O(1) lookup at render time.\n *\n * Pass an empty array (or `undefined`) to skip the query. Missing ids\n * are absent from the map (silently dropped server-side).\n */\nexport function usePatientsByIds(\n ids: string[] | null | undefined,\n): UseQueryResult<Record<string, Patient>> {\n const stableIds = useMemo(() => {\n const arr = ids ?? [];\n return [...new Set(arr)].sort();\n }, [ids]);\n const skipped = stableIds.length === 0;\n\n const result = useQuery(\n patientsGetByIdsRef as FunctionReference<\"query\">,\n skipped ? SKIP : { ids: stableIds },\n ) as Patient[] | undefined;\n\n const mapped = useMemo(() => {\n if (result === undefined) return undefined;\n return Object.fromEntries(\n result.map((p) => {\n const id =\n (p as { id?: string; _id?: string }).id ??\n (p as { _id?: string })._id ??\n \"\";\n return [String(id), p];\n }),\n );\n }, [result]);\n\n if (skipped) {\n return { data: {}, loading: false, error: undefined };\n }\n return {\n data: mapped,\n loading: mapped === undefined,\n error: undefined,\n };\n}\n\n/**\n * Bulk patient lookup by phone numbers (digits or formatted). Returns\n * `Record<phoneDigits, Patient>` for O(1) lookup by digits-only key.\n */\nexport function usePatientsByPhones(\n phones: string[] | null | undefined,\n): UseQueryResult<Record<string, Patient>> {\n const stableDigits = useMemo(() => {\n const arr = phones ?? [];\n const digits = arr\n .map((p) => p.replace(/\\D+/g, \"\"))\n .filter((s) => s.length > 0);\n return [...new Set(digits)].sort();\n }, [phones]);\n const skipped = stableDigits.length === 0;\n\n const result = useQuery(\n patientsGetByPhonesRef as FunctionReference<\"query\">,\n skipped ? SKIP : { phoneDigits: stableDigits },\n ) as Array<{ phoneDigits: string; patient: Patient }> | undefined;\n\n const mapped = useMemo(() => {\n if (result === undefined) return undefined;\n return Object.fromEntries(result.map((r) => [r.phoneDigits, r.patient]));\n }, [result]);\n\n if (skipped) {\n return { data: {}, loading: false, error: undefined };\n }\n return {\n data: mapped,\n loading: mapped === undefined,\n error: undefined,\n };\n}\n","/**\n * TruthProvider — Convex React provider pre-configured for Truth.\n *\n * Wraps children in a ConvexProvider connected to the correct\n * Truth Convex deployment for the given environment.\n *\n * @example\n * ```tsx\n * import { TruthProvider } from '@hipnation-truth/sdk/react';\n *\n * function App() {\n * return (\n * <TruthProvider environment=\"sandbox\">\n * <MyApp />\n * </TruthProvider>\n * );\n * }\n * ```\n */\n\n\"use client\";\n\nimport { ConvexProvider, ConvexReactClient } from \"convex/react\";\nimport type { ReactNode } from \"react\";\nimport { createElement, useMemo } from \"react\";\n\n// Mirrors CONVEX_URLS in client.ts. UAT shares prod resources.\n//\n// Exported so `resolveConvexUrl` can be unit-tested without booting\n// React — a regression in this map shipped SDK 0.4.1 that pointed\n// UAT at sandbox Convex. The test in provider.test.ts locks every\n// stage → URL pair down.\nexport const CONVEX_URLS: Record<string, string> = {\n local: \"https://courteous-duck-623.convex.cloud\",\n staging: \"https://courteous-duck-623.convex.cloud\",\n stg: \"https://courteous-duck-623.convex.cloud\",\n sandbox: \"https://courteous-duck-623.convex.cloud\",\n uat: \"https://gallant-gecko-217.convex.cloud\",\n production: \"https://gallant-gecko-217.convex.cloud\",\n};\n\n/**\n * Resolve the Convex URL for a given environment. Honors an explicit\n * override and falls back to sandbox for unknown environments.\n */\nexport function resolveConvexUrl(\n environment: string | undefined,\n override?: string,\n): string {\n if (override) {\n return override;\n }\n const env = environment ?? \"sandbox\";\n return CONVEX_URLS[env] ?? CONVEX_URLS.sandbox;\n}\n\ninterface TruthProviderProps {\n /** Truth environment — determines which Convex deployment to connect to */\n environment?: string;\n /** Override the Convex URL directly */\n convexUrl?: string;\n children: ReactNode;\n}\n\nfunction TruthProvider({\n environment = \"sandbox\",\n convexUrl,\n children,\n}: TruthProviderProps) {\n const url = resolveConvexUrl(environment, convexUrl);\n\n const client = useMemo(() => new ConvexReactClient(url), [url]);\n\n return createElement(ConvexProvider, { client }, children);\n}\n\nexport { TruthProvider };\nexport type { TruthProviderProps };\n","/**\n * React hooks for conversation reminders — bulk lookup keyed by\n * conversation id. Wraps the public `reminders:listPendingByConversationIds`\n * Convex query so CommHub's inbox can render the reminder clock icon\n * with one reactive subscription for the entire visible list.\n *\n * Must be used within `<TruthProvider />` (see `./provider`).\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { Reminder } from \"../types/reminder\";\nimport type { UseQueryResult } from \"./conversations\";\n\nconst remindersListPendingByConversationIdsRef = makeFunctionReference<\n \"query\",\n { conversationIds: string[] },\n Reminder[]\n>(\"reminders:listPendingByConversationIds\");\n\nconst SKIP = \"skip\" as const;\n\n/**\n * Subscribe to the latest pending reminder for each of the given\n * conversation ids. Returns a `Record<conversationId, Reminder>` —\n * conversations with no pending reminder are absent from the map.\n *\n * Pass an empty array (or `undefined`) to skip the query — useful while\n * the inbox list is still loading.\n *\n * @example\n * ```tsx\n * const ids = conversations.map((c) => c.id);\n * const { data: remindersByConv } = useRemindersForConversations(ids);\n * const r = remindersByConv?.[conv.id]; // Reminder | undefined\n * ```\n */\nexport function useRemindersForConversations(\n conversationIds: string[] | null | undefined,\n): UseQueryResult<Record<string, Reminder>> {\n const ids = conversationIds ?? [];\n const skipped = ids.length === 0;\n\n const result = useQuery(\n remindersListPendingByConversationIdsRef as FunctionReference<\"query\">,\n skipped ? SKIP : { conversationIds: ids },\n ) as Reminder[] | undefined;\n\n const mapped =\n result === undefined\n ? undefined\n : Object.fromEntries(result.map((r) => [r.conversationId, r]));\n\n if (skipped) {\n return { data: {}, loading: false, error: undefined };\n }\n return {\n data: mapped,\n loading: mapped === undefined,\n error: undefined,\n };\n}\n","/**\n * React tracking hook — fire-and-forget event tracking from client components.\n *\n * Creates a lightweight Tracker instance scoped to the browser session\n * and exposes a useTrack() hook for emitting events to Truth's Kinesis stream.\n *\n * @example\n * ```tsx\n * import { useTruth } from '@hipnation-truth/sdk/react';\n *\n * function ConversationView({ conversationId }) {\n * const truth = useTruth();\n *\n * useEffect(() => {\n * truth.track('conversation.marked_read.v1', {\n * read_by_actor_id: userId,\n * unread_count_before: 5,\n * unread_count_after: 0,\n * }, { subject: { conversation_id: conversationId } });\n * }, [conversationId]);\n * }\n * ```\n */\n\n\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { createContext, createElement, useContext, useMemo } from \"react\";\nimport type {\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"../tracking/events\";\nimport { Tracker } from \"../tracking/tracker\";\nimport type { ActorContext } from \"../types/config\";\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface TruthTrackingContextValue {\n track: <T extends EventType>(\n eventType: T,\n payload: EventPayloadMap[T],\n options?: TrackOptions,\n ) => void;\n identify: (actorId: string, actorType: ActorContext[\"actorType\"]) => void;\n}\n\nconst TruthTrackingContext = createContext<TruthTrackingContextValue | null>(\n null,\n);\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\ninterface TruthTrackingProviderProps {\n /** Truth environment — determines API URL for event delivery */\n environment?: string;\n /** Event source identifier */\n source?: string;\n /** Source version (git SHA) */\n sourceVersion?: string;\n /** Default tenant ID */\n tenantId?: string;\n /** API key for authentication */\n apiKey?: string;\n children: ReactNode;\n}\n\nfunction TruthTrackingProvider({\n environment = \"sandbox\",\n source = \"communication-hub.frontend\",\n sourceVersion = \"unknown\",\n tenantId = \"hipnation\",\n apiKey = \"\",\n children,\n}: TruthTrackingProviderProps) {\n const value = useMemo<TruthTrackingContextValue>(() => {\n const tracker = new Tracker({\n apiKey,\n environment,\n source,\n sourceVersion,\n tenantId,\n batchSize: 10,\n flushIntervalMs: 5000,\n });\n\n return {\n track: (eventType, payload, options) => {\n tracker.track(eventType, payload, options);\n },\n identify: (actorId, actorType) => {\n tracker.setActor({ actorId, actorType });\n },\n };\n }, [apiKey, environment, source, sourceVersion, tenantId]);\n\n return createElement(TruthTrackingContext.Provider, { value }, children);\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Access the Truth tracking context. Must be within a TruthTrackingProvider.\n * Returns `{ track, identify }` for emitting events to Kinesis.\n */\nfunction useTruth(): TruthTrackingContextValue {\n const ctx = useContext(TruthTrackingContext);\n if (!ctx) {\n throw new Error(\"useTruth must be used within a TruthTrackingProvider\");\n }\n return ctx;\n}\n\nexport { TruthTrackingProvider, useTruth };\nexport type { TruthTrackingProviderProps, TruthTrackingContextValue };\n","/**\n * Event tracker with batching, retry, and flush support.\n *\n * Buffers events in an internal queue and flushes them to the Truth API\n * endpoint. Flushes occur when the buffer reaches `batchSize` or every\n * `flushIntervalMs` milliseconds, whichever comes first.\n */\n\nimport type { ActorContext } from \"../types/config\";\nimport type {\n EventEnvelope,\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"./events\";\n\n// ---------------------------------------------------------------------------\n// UUID v7 helper (no external dependency)\n// ---------------------------------------------------------------------------\n\n/**\n * Generates a UUID v7 string. Uses crypto.randomUUID where available,\n * falling back to a timestamp + random bytes implementation.\n *\n * UUID v7 layout (RFC 9562):\n * 48 bits - unix timestamp (ms)\n * 4 bits - version (0b0111)\n * 12 bits - random\n * 2 bits - variant (0b10)\n * 62 bits - random\n */\nfunction generateUuidV7(): string {\n const now = Date.now();\n\n // 6 bytes of timestamp\n const timeBytes = new Uint8Array(6);\n let ts = now;\n for (let i = 5; i >= 0; i--) {\n timeBytes[i] = ts & 0xff;\n ts = Math.floor(ts / 256);\n }\n\n // 10 bytes of random\n const randomBytes = new Uint8Array(10);\n if (\n typeof globalThis.crypto !== \"undefined\" &&\n globalThis.crypto.getRandomValues\n ) {\n globalThis.crypto.getRandomValues(randomBytes);\n } else {\n for (let i = 0; i < 10; i++) {\n randomBytes[i] = Math.floor(Math.random() * 256);\n }\n }\n\n // Assemble 16 bytes\n const bytes = new Uint8Array(16);\n bytes.set(timeBytes, 0);\n bytes.set(randomBytes, 6);\n\n // Set version (bits 48-51 to 0b0111)\n bytes[6] = (bytes[6] & 0x0f) | 0x70;\n\n // Set variant (bits 64-65 to 0b10)\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n\n // Format as hex string with dashes\n const hex = Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20, 32),\n ].join(\"-\");\n}\n\n// ---------------------------------------------------------------------------\n// Environment-based API URL resolution\n// ---------------------------------------------------------------------------\n\n// Environment → Truth API base URL.\n//\n// Topology:\n// local / staging / stg / sandbox → sandbox Truth (app.sandbox.*)\n// uat / production → production Truth (app.truth.*)\n//\n// UAT shares production resources (same Fly Postgres + Hasura + Truth +\n// Convex + EHR tokens as prod). Staging is the isolated sandbox env.\nconst API_URLS: Record<string, string> = {\n local: \"http://localhost:3000\",\n staging: \"https://app.sandbox.communication-hub.com\",\n stg: \"https://app.sandbox.communication-hub.com\",\n sandbox: \"https://app.sandbox.communication-hub.com\",\n uat: \"https://app.truth.communication-hub.com\",\n production: \"https://app.truth.communication-hub.com\",\n};\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_BATCH_SIZE = 25;\nconst DEFAULT_FLUSH_INTERVAL_MS = 5_000;\nconst MAX_RETRIES = 3;\nconst BASE_RETRY_DELAY_MS = 500;\n\n// ---------------------------------------------------------------------------\n// Tracker configuration\n// ---------------------------------------------------------------------------\n\ninterface TrackerConfig {\n apiKey: string;\n environment: string;\n source: string;\n sourceVersion: string;\n tenantId: string;\n batchSize: number;\n flushIntervalMs: number;\n apiBaseUrl?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Tracker class\n// ---------------------------------------------------------------------------\n\nclass Tracker {\n private readonly config: TrackerConfig;\n readonly apiUrl: string;\n private queue: EventEnvelope[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private defaultActor: ActorContext | undefined;\n private isFlushing = false;\n private isShutdown = false;\n\n constructor(config: TrackerConfig) {\n this.config = config;\n this.apiUrl =\n config.apiBaseUrl ?? API_URLS[config.environment] ?? API_URLS.local;\n\n this.startFlushInterval();\n this.registerShutdownHooks();\n }\n\n /**\n * Set the default actor context for subsequent events.\n */\n setActor(actor: ActorContext): void {\n this.defaultActor = actor;\n }\n\n /**\n * Enqueue a typed event for delivery. This is fire-and-forget from\n * the caller's perspective -- events are buffered and flushed in batches.\n */\n track<T extends EventType>(\n eventType: T,\n payload: EventPayloadMap[T],\n options?: TrackOptions,\n ): void {\n if (this.isShutdown) {\n return;\n }\n\n const now = new Date().toISOString();\n\n const envelope: EventEnvelope = {\n event_id: generateUuidV7(),\n event_type: eventType,\n schema_version: 1,\n occurred_at: options?.occurredAt ?? now,\n received_at: now,\n source: this.config.source,\n source_version: this.config.sourceVersion,\n tenant_id: options?.tenantId ?? this.config.tenantId,\n actor:\n options?.actor ??\n (this.defaultActor\n ? {\n actor_id: this.defaultActor.actorId,\n actor_type: this.defaultActor.actorType,\n }\n : undefined),\n subject: options?.subject,\n compliance: options?.compliance,\n payload: payload as unknown as Record<string, unknown>,\n };\n\n this.queue.push(envelope);\n\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Force an immediate flush of all buffered events.\n * Returns a promise that resolves when the flush completes.\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0 || this.isFlushing) {\n return;\n }\n\n this.isFlushing = true;\n const batch = this.queue.splice(0, this.config.batchSize);\n\n try {\n await this.sendBatch(batch);\n } catch {\n // Re-queue events that failed to send (prepend to maintain ordering)\n this.queue.unshift(...batch);\n } finally {\n this.isFlushing = false;\n }\n\n // If there are still events in the queue, flush again\n if (this.queue.length >= this.config.batchSize) {\n await this.flush();\n }\n }\n\n /**\n * Gracefully shut down the tracker. Flushes remaining events and\n * clears the flush interval.\n */\n async shutdown(): Promise<void> {\n this.isShutdown = true;\n this.stopFlushInterval();\n await this.flush();\n }\n\n /**\n * Send a batch of events to the Truth API with exponential backoff retry.\n */\n private async sendBatch(batch: EventEnvelope[]): Promise<void> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetch(`${this.apiUrl}/api/events/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.config.apiKey,\n },\n body: JSON.stringify({ events: batch }),\n });\n\n if (response.ok) {\n return;\n }\n\n // Don't retry 4xx client errors (except 429)\n if (\n response.status >= 400 &&\n response.status < 500 &&\n response.status !== 429\n ) {\n return;\n }\n\n lastError = new Error(\n `HTTP ${response.status}: ${response.statusText}`,\n );\n } catch (error) {\n lastError = error;\n }\n\n // Exponential backoff with jitter\n if (attempt < MAX_RETRIES) {\n const delay = BASE_RETRY_DELAY_MS * 2 ** attempt;\n const jitter = Math.random() * delay * 0.5;\n await sleep(delay + jitter);\n }\n }\n\n throw lastError;\n }\n\n private startFlushInterval(): void {\n if (this.config.flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n void this.flush();\n }, this.config.flushIntervalMs);\n\n // Unref the timer so it doesn't prevent Node.js from exiting\n if (typeof this.flushTimer === \"object\" && \"unref\" in this.flushTimer) {\n this.flushTimer.unref();\n }\n }\n }\n\n private stopFlushInterval(): void {\n if (this.flushTimer !== null) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n }\n\n private registerShutdownHooks(): void {\n // Only register in Node.js environments\n if (typeof globalThis.process !== \"undefined\" && globalThis.process.on) {\n const shutdownHandler = () => {\n void this.shutdown();\n };\n\n globalThis.process.on(\"beforeExit\", shutdownHandler);\n globalThis.process.on(\"SIGTERM\", shutdownHandler);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport {\n Tracker,\n generateUuidV7,\n DEFAULT_BATCH_SIZE,\n DEFAULT_FLUSH_INTERVAL_MS,\n};\nexport type { TrackerConfig };\n","/**\n * useUserSettings — reactive hook for per-user notification preferences\n * stored in the Truth `userSettings` Convex table.\n *\n * Must be used within `<TruthProvider />` (see `./provider`).\n *\n * @example\n * ```tsx\n * const { data: settings, loading } = useUserSettings(userId);\n * // settings?.notificationsEnabled — boolean or undefined (no row yet)\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\nimport type { UserSettings } from \"../resources/user-settings\";\n\n// ---------------------------------------------------------------------------\n// Convex function reference\n// ---------------------------------------------------------------------------\n\nconst userSettingsGetByUserIdRef = makeFunctionReference<\n \"query\",\n { userId: string },\n UserSettings | null\n>(\"userSettings:getByUserId\");\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nconst SKIP = \"skip\" as const;\n\n/**\n * Subscribe to the `userSettings` row for `userId`. Returns `null` when\n * no row exists (treat as all-defaults). Returns `undefined` while the\n * Convex query is in-flight.\n *\n * Pass `null` or `undefined` as `userId` to skip the query (e.g. while\n * auth is loading).\n */\nexport function useUserSettings(\n userId: string | null | undefined,\n): UseQueryResult<UserSettings | null> {\n const skip = !userId;\n\n const result = useQuery(\n userSettingsGetByUserIdRef as FunctionReference<\"query\">,\n skip ? SKIP : { userId: userId as string },\n ) as UserSettings | null | undefined;\n\n if (skip) {\n return { data: null, loading: false, error: undefined };\n }\n\n return {\n data: result ?? null,\n loading: result === undefined,\n error: undefined,\n };\n}\n","/**\n * React hook for fetching an authenticated Dialpad voicemail URL.\n *\n * Provides an imperative `fetchUrl` callback that wraps\n * `client.messages.getVoicemailUrl()` in React state so CommHub's\n * `AudioPlayer` can replace its urql `useMutation` with a single hook\n * call, without adding a query library dependency.\n *\n * The TruthClient instance is passed directly so the hook is testable\n * and doesn't require a global singleton or React context.\n *\n * @example\n * ```tsx\n * import { useVoicemailUrl } from '@hipnation-truth/sdk/react/voicemail';\n * import { getTruthClient } from '@/lib/truthClient';\n *\n * function AudioPlayer({ uri }: { uri: string }) {\n * const { fetchUrl, url, isLoading, error } = useVoicemailUrl(getTruthClient());\n *\n * const handlePlay = async () => {\n * const authenticated = await fetchUrl(uri);\n * if (authenticated) { ... }\n * };\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { TruthClient } from \"../client\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseVoicemailUrlResult {\n /** Trigger the URL fetch. Resolves with the clean playback URL or null on error. */\n fetchUrl: (voicemailLink: string) => Promise<string | null>;\n /** The most recently fetched URL (null until first successful fetch). */\n url: string | null;\n /** True while the fetch is in-flight. */\n isLoading: boolean;\n /** Error message from the last failed fetch, or null. */\n error: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Provides an imperative `fetchUrl` callback that authenticates a Dialpad\n * voicemail link via the Truth SDK `messages.getVoicemailUrl()` method.\n *\n * @param client The TruthClient instance (e.g., from `getTruthClient()`).\n */\nexport function useVoicemailUrl(client: TruthClient): UseVoicemailUrlResult {\n const [url, setUrl] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Guard against concurrent fetches (user double-tapping play).\n const inFlightRef = useRef(false);\n\n const fetchUrl = useCallback(\n async (voicemailLink: string): Promise<string | null> => {\n if (inFlightRef.current) return null;\n inFlightRef.current = true;\n setIsLoading(true);\n setError(null);\n\n try {\n const result = await client.messages.getVoicemailUrl(voicemailLink);\n setUrl(result.url);\n return result.url;\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n setError(msg);\n return null;\n } finally {\n setIsLoading(false);\n inFlightRef.current = false;\n }\n },\n [client],\n );\n\n return { fetchUrl, url, isLoading, error };\n}\n"],"mappings":"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","names":["import_react","import_server","SKIP","toResult","import_react","import_server","import_react","_a","res","import_react","import_server","SKIP","import_react","import_server","SKIP","import_react","import_server","SKIP","import_react","import_react","import_server","SKIP","import_react","import_react","import_server","SKIP","import_react"]}
|
|
1
|
+
{"version":3,"sources":["../src/react.ts","../src/react/calls.ts","../src/react/conversations.ts","../src/react/hooks.ts","../src/react/notifications.ts","../src/web-push.ts","../src/react/patient-family.ts","../src/react/patient-search.ts","../src/react/patients-bulk.ts","../src/react/provider.ts","../src/react/reminders.ts","../src/react/tracking.ts","../src/tracking/tracker.ts","../src/react/user-settings.ts","../src/react/voicemail.ts"],"sourcesContent":["/**\n * @hipnation-truth/sdk/react\n *\n * React hooks and provider for Truth SDK — real-time Convex-backed data.\n *\n * @example\n * ```tsx\n * import { TruthProvider, usePatients, useAppointments } from '@hipnation-truth/sdk/react';\n *\n * function App() {\n * return (\n * <TruthProvider environment=\"sandbox\">\n * <PatientList />\n * </TruthProvider>\n * );\n * }\n *\n * function PatientList() {\n * const patients = usePatients({ limit: 20 });\n * if (!patients) return <div>Loading...</div>;\n * return patients.map(p => <div key={p._id}>{p.firstName}</div>);\n * }\n * ```\n */\n\n// Dialpad call event hooks (Convex-backed)\nexport type {\n DialpadCallLogRow,\n DialpadCallRow,\n UseActiveCallsOptions,\n} from \"./react/calls\";\nexport {\n ACTIVE_CALL_STATES,\n CONNECTED_CALL_STATES,\n DialpadCallState,\n RINGING_CALL_STATES,\n TERMINAL_CALL_STATES,\n useActiveCalls,\n useDialpadCallByCallId,\n useDialpadCallLog,\n useDialpadCallsForConversation,\n} from \"./react/calls\";\n// Reactive conversation / message hooks (Convex-backed, PR #110 schema)\nexport type {\n ConversationListItem,\n ConversationMessageRow,\n ConversationNoteRow,\n ConversationRow,\n ConversationTaskForUserRow,\n ConversationTaskRow,\n UseConversationsFilters,\n UseMessagesOptions,\n UseQueryResult,\n} from \"./react/conversations\";\nexport {\n useConversationByPhonePair,\n useConversationNotes,\n useConversationNotesByPhonePair,\n useConversations,\n useConversationTasks,\n useConversationTasksByPhonePair,\n useConversationTasksForUser,\n useMessages,\n useUnreadAggregate,\n useUnreadCount,\n} from \"./react/conversations\";\n// Re-export types consumers commonly need\nexport type {\n ConversationMessage,\n Physician,\n UseAppointmentListOptions,\n UseConversationMessagesOptions,\n UsePatientBasicOptions,\n UsePatientBasicResult,\n UsePatientListOptions,\n UsePatientMedicalOptions,\n UsePatientPhotoOptions,\n} from \"./react/hooks\";\n// Patient / Appointment / Physician / Medical / Messages hooks\nexport {\n useAppointment,\n useAppointmentByElationId,\n useAppointments,\n useConversationMessages,\n usePatient,\n usePatientBasic,\n usePatientByElationId,\n usePatientByHintId,\n usePatientMedical,\n usePatientPhoto,\n usePatients,\n usePharmacyByNcpdpId,\n usePhysicianByElationId,\n usePhysiciansByElationIds,\n} from \"./react/hooks\";\n// Notifications\nexport type {\n PermissionStatus,\n UseNotificationsOptions,\n UseNotificationsResult,\n} from \"./react/notifications\";\nexport { useNotifications } from \"./react/notifications\";\n// Patient family members\nexport type {\n FamilyMemberRow,\n UsePatientFamilyMembersInput,\n} from \"./react/patient-family\";\nexport { usePatientFamilyMembers } from \"./react/patient-family\";\n// Patient search (multi-mode, office-scoped)\nexport type {\n PatientSearchResult,\n UsePatientSearchOptions,\n} from \"./react/patient-search\";\nexport { usePatientSearch } from \"./react/patient-search\";\n// Bulk patient lookup\nexport {\n usePatientsByIds,\n usePatientsByPhones,\n} from \"./react/patients-bulk\";\nexport type { TruthProviderProps } from \"./react/provider\";\n// Provider\nexport { TruthProvider } from \"./react/provider\";\n// Reminders\nexport { useRemindersForConversations } from \"./react/reminders\";\nexport type {\n TruthTrackingContextValue,\n TruthTrackingProviderProps,\n} from \"./react/tracking\";\n// Tracking\nexport { TruthTrackingProvider, useTruth } from \"./react/tracking\";\n// User settings\nexport { useUserSettings } from \"./react/user-settings\";\n// Voicemail URL resolver\nexport { useVoicemailUrl } from \"./react/voicemail\";\n// Re-export event types for track() calls\nexport type {\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"./tracking/events\";\nexport type { Appointment, AppointmentListOptions } from \"./types/appointment\";\nexport type { Patient, PatientListOptions } from \"./types/patient\";\n","/**\n * React hooks for Dialpad call events — replaces CommHub's Hasura\n * `useDialpad_EventsSubscription`. Powered by the new\n * `dialpadCallEvents` + `dialpadCallEventLog` Convex tables that the\n * Truth webhook handler writes on every call state transition.\n *\n * Same `{ data, loading, error }` contract as the other hooks in this\n * SDK; same `skip` semantics on missing arguments.\n *\n * @example\n * ```tsx\n * import { useActiveCalls, useDialpadCallByCallId } from '@hipnation-truth/sdk/react';\n *\n * function IncomingBanner() {\n * const { data: active } = useActiveCalls();\n * if (!active?.length) return null;\n * return <Banner calls={active} />;\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n\n// ---------------------------------------------------------------------------\n// Call state — single source of truth, mirrors the Dialpad webhook\n// `state` field and the values written to the Convex `dialpadCallEvents`\n// table by the Truth webhook handler. Consumers can switch on the\n// const-as-enum or compare the row's raw `state` string directly.\n// ---------------------------------------------------------------------------\n\nexport const DialpadCallState = {\n Calling: \"calling\",\n Ringing: \"ringing\",\n Connected: \"connected\",\n Hold: \"hold\",\n Hangup: \"hangup\",\n Missed: \"missed\",\n VoicemailUploaded: \"voicemail_uploaded\",\n} as const;\nexport type DialpadCallState =\n (typeof DialpadCallState)[keyof typeof DialpadCallState];\n\n/** Ringing states — caller hasn't been picked up yet. */\nexport const RINGING_CALL_STATES: ReadonlySet<DialpadCallState> = new Set([\n DialpadCallState.Calling,\n DialpadCallState.Ringing,\n]);\n\n/** Connected states — call is live (or on hold). */\nexport const CONNECTED_CALL_STATES: ReadonlySet<DialpadCallState> = new Set([\n DialpadCallState.Connected,\n]);\n\n/**\n * Active states — anything not terminal. Default filter for\n * `useActiveCalls`; the union of ringing + connected. `hold` is NOT\n * included by default because the IncomingCalls UI doesn't surface\n * held calls in the inbox banner.\n */\nexport const ACTIVE_CALL_STATES: ReadonlySet<DialpadCallState> = new Set([\n ...RINGING_CALL_STATES,\n ...CONNECTED_CALL_STATES,\n]);\n\n/** Terminal states — call has ended. Used to exclude rows from active lists. */\nexport const TERMINAL_CALL_STATES: ReadonlySet<DialpadCallState> = new Set([\n DialpadCallState.Hangup,\n DialpadCallState.Missed,\n DialpadCallState.VoicemailUploaded,\n]);\n\n// ---------------------------------------------------------------------------\n// Row shapes — mirror the Convex `dialpadCallEvents` table on the Truth\n// schema. Once the consuming app regenerates `convex/_generated/api.d.ts`\n// these can be inferred.\n// ---------------------------------------------------------------------------\n\nexport interface DialpadCallRow {\n _id: string;\n _creationTime: number;\n callId: string;\n state: string;\n direction: string | null;\n fromNumber: string | null;\n toNumber: string | null;\n phonePair: string | null;\n conversationId: string | null;\n patientId: string | null;\n voicemailLink: string | null;\n voicemailDurationSec: number | null;\n transcript: unknown | null;\n duration: number | null;\n occurredAt: string;\n updatedAt: string;\n}\n\nexport interface DialpadCallLogRow {\n _id: string;\n _creationTime: number;\n callId: string;\n state: string;\n direction: string | null;\n payload: unknown | null;\n occurredAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// Convex refs\n// ---------------------------------------------------------------------------\n\nconst listActiveRef = makeFunctionReference<\n \"query\",\n { limit?: number; terminalStates?: string[] },\n DialpadCallRow[]\n>(\"dialpadCallEvents:listActive\");\n\nconst listForConversationRef = makeFunctionReference<\n \"query\",\n { conversationId: string; limit?: number },\n DialpadCallRow[]\n>(\"dialpadCallEvents:listForConversation\");\n\nconst getByCallIdRef = makeFunctionReference<\n \"query\",\n { callId: string },\n DialpadCallRow | null\n>(\"dialpadCallEvents:getByCallId\");\n\nconst listLogForCallIdRef = makeFunctionReference<\n \"query\",\n { callId: string; limit?: number },\n DialpadCallLogRow[]\n>(\"dialpadCallEvents:listLogForCallId\");\n\n// ---------------------------------------------------------------------------\n// Helpers — duplicated from conversations.ts to keep this file independent\n// ---------------------------------------------------------------------------\n\nconst SKIP = \"skip\" as const;\n\nfunction toResult<T>(\n value: T | undefined,\n skipped: boolean,\n): UseQueryResult<T> {\n if (skipped) {\n return { data: undefined, loading: false, error: undefined };\n }\n return {\n data: value,\n loading: value === undefined,\n error: undefined,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\nexport interface UseActiveCallsOptions {\n /** Page cap. Default 50. */\n limit?: number;\n /**\n * Terminal states excluded from the active list. Override only if\n * Dialpad introduces a new state.\n */\n terminalStates?: string[];\n}\n\n/**\n * Live list of every Dialpad call that hasn't reached a terminal state\n * — used by CommHub's IncomingCalls banner. Updates as the webhook\n * handler patches the per-call row.\n */\nexport function useActiveCalls(\n options?: UseActiveCallsOptions,\n): UseQueryResult<DialpadCallRow[]> {\n const result = useQuery(\n listActiveRef as FunctionReference<\"query\">,\n options ?? {},\n ) as DialpadCallRow[] | undefined;\n return toResult(result, false);\n}\n\n/**\n * All calls (most-recent-first) on a single conversation. Pass the\n * Convex conversation `_id` (e.g. from `useConversationByPhonePair`).\n */\nexport function useDialpadCallsForConversation(\n conversationId: string | null | undefined,\n options?: { limit?: number },\n): UseQueryResult<DialpadCallRow[]> {\n const skipped = !conversationId;\n const result = useQuery(\n listForConversationRef as FunctionReference<\"query\">,\n skipped\n ? SKIP\n : {\n conversationId: conversationId as string,\n limit: options?.limit,\n },\n ) as DialpadCallRow[] | undefined;\n return toResult(result, skipped);\n}\n\n/** Single call lookup by Dialpad `call_id`. */\nexport function useDialpadCallByCallId(\n callId: string | null | undefined,\n): UseQueryResult<DialpadCallRow | null> {\n const skipped = !callId;\n const result = useQuery(\n getByCallIdRef as FunctionReference<\"query\">,\n skipped ? SKIP : { callId: callId as string },\n ) as DialpadCallRow | null | undefined;\n return toResult(result, skipped);\n}\n\n/**\n * Full audit history of state transitions for a call — backs the\n * hold / recording / transcription timeline in the right panel.\n */\nexport function useDialpadCallLog(\n callId: string | null | undefined,\n options?: { limit?: number },\n): UseQueryResult<DialpadCallLogRow[]> {\n const skipped = !callId;\n const result = useQuery(\n listLogForCallIdRef as FunctionReference<\"query\">,\n skipped ? SKIP : { callId: callId as string, limit: options?.limit },\n ) as DialpadCallLogRow[] | undefined;\n return toResult(result, skipped);\n}\n","/**\n * React hooks for Truth SDK — reactive conversation + message data.\n *\n * Wraps Convex queries owned by the Truth backend so consumers (CommHub\n * Expo frontend) get live-updating conversation lists, message threads,\n * and unread totals without managing subscriptions themselves.\n *\n * **Hook contract:** every hook in this file returns\n * `{ data, loading, error }`. `data` is `undefined` while loading and\n * also when the underlying query is `\"skip\"`'d (caller didn't pass the\n * required arg yet). `loading` is `true` only while a real subscription\n * is in-flight; once `data` resolves (even to `null`) `loading` flips\n * to `false`. `error` is reserved for SDK-side validation — Convex\n * itself throws on subscribe rather than returning an error value, so\n * unhandled query errors propagate as React errors and should be caught\n * with an error boundary.\n *\n * **Backed by PR #110 schema (commit `41dbb59` on\n * `feat/migrate-dialpad-webhook-and-messages-to-truth`):**\n * - `conversations:listForUser` → `useConversations`\n * - `conversations:getUnreadTotalForUser` → `useUnreadCount`\n * - `conversations:getByPhonePair` → `useConversationByPhonePair`\n * - `conversationMessages:getByConversationId` → `useMessages`\n *\n * **Deliberately NOT shipped here (no backend query yet — flagged in\n * PR #111 body for follow-up):**\n * - Single-conversation-by-id lookup. Convex `conversations` has no\n * `get(id)` query — only `getByPhonePair`. CommHub uses phonePair as\n * the primary handle, so the byPair flavor is what consumers need;\n * if id-based lookup is needed later we can add a `conversations:get`\n * in Truth.\n * - Participants / \"seen by\" hook. The migrated schema has no\n * `conversationParticipants` table — read state is per-user via\n * `conversationReads` and there's no public list query for it yet.\n * - `markRead` mutation. PR #110 declares `markRead` as\n * `internalMutation`, so the frontend can't invoke it directly — it\n * has to go through a Truth HTTP endpoint, or webhook-migrator needs\n * to flip it to a public `mutation`. Flagged for a follow-up.\n *\n * Must be used within `<TruthProvider />` (see `./provider`).\n *\n * @example\n * ```tsx\n * import {\n * useConversations,\n * useConversationByPhonePair,\n * useMessages,\n * useUnreadCount,\n * } from '@hipnation-truth/sdk/react';\n *\n * function Inbox({ userId }: { userId: string }) {\n * const { data: convos, loading } = useConversations({ userId });\n * const { data: unread } = useUnreadCount(userId);\n * if (loading) return <Spinner />;\n * return (\n * <div>\n * <Badge count={unread ?? 0} />\n * {convos?.map((c) => <ConvoRow key={c.id} convo={c} />)}\n * </div>\n * );\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport { useMemo } from \"react\";\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n//\n// Mirror the shapes returned by the Convex queries on PR #110. Once\n// the consuming app regenerates `convex/_generated/api.d.ts` against\n// the merged schema these can be replaced with inferred types — for\n// today they give consumers useful autocomplete.\n\n/**\n * Row shape returned by `conversations:listForUser` — joins the base\n * `conversations` row with the caller's per-user `conversationReads`\n * entry so unread state lives on each item.\n */\nexport interface ConversationListItem {\n /** Convex id of the conversation row. */\n id: string;\n patientPhone: string;\n providerPhone: string;\n /** Sorted-digit-pair key (commutative — same regardless of direction). */\n phonePair: string;\n /** Truth-side patient id, when the kinesis pipeline could resolve it. */\n patientId: string | null;\n /** ISO timestamp of the most recent message in the conversation. */\n lastMessageAt: string;\n /** Caller's unread count for this conversation. */\n unreadCount: number;\n /** ISO timestamp of when the caller last marked the conversation read. */\n lastReadAt: string | null;\n}\n\n/**\n * Raw `conversations` table row — what `getByPhonePair` returns. No\n * unread / read joining; for that, prefer `useConversations`.\n */\nexport interface ConversationRow {\n _id: string;\n _creationTime: number;\n patientPhone: string;\n providerPhone: string;\n phonePair: string;\n patientId: string | null;\n lastEventId: string | null;\n lastMessageAt: string;\n createdAt: string;\n}\n\n/**\n * Message row returned by `conversationMessages:getByConversationId` —\n * calls + SMS merged chronologically. Mirrors `ConversationMessage`\n * already exported from the SDK; re-exported here under a clearer name\n * for the new hook surface.\n */\n/**\n * Conversation note row — single entry shown in the right-panel notes\n * tab in CommHub. Replaces the urql Hasura `internal_activities` row\n * with `type='note'`.\n */\nexport interface ConversationNoteRow {\n /** Convex id of the conversationNotes row. */\n id: string;\n conversationId: string;\n /** Dialpad event the note is attached to, when present. */\n eventId: string | null;\n author: string;\n content: string;\n /** Free-form status flag — matches CommHub's existing schema. */\n status: string | null;\n /** Free-form `type` discriminator — matches CommHub's existing schema. */\n type: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Conversation task row — single entry shown in the right-panel tasks\n * tab. Replaces the urql Hasura `internal_activities` row with\n * `type='task'`.\n */\nexport interface ConversationTaskRow {\n id: string;\n conversationId: string;\n eventId: string | null;\n author: string;\n description: string;\n status: \"pending\" | \"completed\";\n priority: \"high\" | \"medium\" | \"low\" | null;\n assignee: string | null;\n resolvedBy: string | null;\n type: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Cross-conversation task row used by the \"My Tasks\" tab. Adds the\n * conversation's normalized phone pair so the UI can render the\n * patient handle without a second reactive query.\n */\nexport interface ConversationTaskForUserRow extends ConversationTaskRow {\n /** Normalized `(patient_phone | provider_phone)` for the task's conversation. */\n phonePair: string | null;\n}\n\nexport interface ConversationMessageRow {\n kind: \"call\" | \"sms\";\n id: string;\n providerId: string;\n state: string | null;\n direction: string | null;\n fromNumber: string | null;\n toNumber: string | null;\n voicemailLink: string | null;\n duration: number | null;\n text: string | null;\n mms: boolean;\n mmsUrl: string | null;\n messageStatus: string | null;\n occurredAt: string;\n conversationId: string | null;\n patientId: string | null;\n}\n\nexport interface UseQueryResult<T> {\n /**\n * Query data. `undefined` while loading or while the query is\n * intentionally skipped (e.g. caller hasn't supplied `userId` yet).\n */\n data: T | undefined;\n /** True only while a real subscription is in-flight. */\n loading: boolean;\n /**\n * Reserved for client-side validation errors surfaced by the SDK\n * itself. Convex query errors propagate as React errors and should\n * be caught with an error boundary.\n */\n error: Error | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Convex function references — string-keyed, decoupled from the\n// consuming app's generated `convex/_generated/api`. Names below match\n// the queries on PR #110 (commit 41dbb59) verbatim.\n// ---------------------------------------------------------------------------\n\nconst conversationsListForUserRef = makeFunctionReference<\n \"query\",\n { userId: string; limit?: number },\n ConversationListItem[]\n>(\"conversations:listForUser\");\n\nconst conversationsSearchForUserRef = makeFunctionReference<\n \"query\",\n { userId: string; search: string; limit?: number },\n ConversationListItem[]\n>(\"conversations:searchForUser\");\n\nconst conversationsGetUnreadTotalForUserRef = makeFunctionReference<\n \"query\",\n { userId: string },\n number\n>(\"conversations:getUnreadTotalForUser\");\n\nconst conversationsGetUnreadAggregateForUserRef = makeFunctionReference<\n \"query\",\n { userId: string; providerPhones?: string[] },\n { totalMessages: number; conversationCount: number }\n>(\"conversations:getUnreadAggregateForUser\");\n\nconst conversationsGetByPhonePairRef = makeFunctionReference<\n \"query\",\n { phonePair: string },\n ConversationRow | null\n>(\"conversations:getByPhonePair\");\n\n// Lives on the existing `conversationMessages` module (pre-#110). PR\n// #110 added `conversationId` indexing on `messageSms` / `messageCalls`,\n// so this query is now reliable for the new conversations table.\nconst conversationMessagesGetByConversationIdRef = makeFunctionReference<\n \"query\",\n { conversationId: string; limit?: number },\n ConversationMessageRow[]\n>(\"conversationMessages:getByConversationId\");\n\n// Notes + tasks for a conversation thread (Truth #730). Both lists are\n// keyed on the Convex conversation `_id` — same id that comes back\n// from `useConversations`.\nconst conversationNotesListForConversationRef = makeFunctionReference<\n \"query\",\n { conversationId: string },\n ConversationNoteRow[]\n>(\"conversationNotes:listForConversation\");\n\nconst conversationTasksListForConversationRef = makeFunctionReference<\n \"query\",\n { conversationId: string },\n ConversationTaskRow[]\n>(\"conversationTasks:listForConversation\");\n\n// Phone-pair-keyed variants (Truth SDK 0.10.0). CommHub holds the\n// conversation by `(patient_phone, provider_phone)` and shouldn't have\n// to chain `useConversationByPhonePair → useConversationNotes`. These\n// resolve the conversation server-side and return the same row shape.\nconst conversationNotesListByPhonePairRef = makeFunctionReference<\n \"query\",\n { phonePair: string },\n ConversationNoteRow[]\n>(\"conversationNotes:listByPhonePair\");\n\nconst conversationTasksListByPhonePairRef = makeFunctionReference<\n \"query\",\n { phonePair: string },\n ConversationTaskRow[]\n>(\"conversationTasks:listByPhonePair\");\n\nconst conversationTasksListForUserRef = makeFunctionReference<\n \"query\",\n { userId: string; limit?: number },\n ConversationTaskForUserRow[]\n>(\"conversationTasks:listForUser\");\n\n// ---------------------------------------------------------------------------\n// Skipped-query sentinel + result shaping\n// ---------------------------------------------------------------------------\n\nconst SKIP = \"skip\" as const;\n\nfunction toResult<T>(\n value: T | undefined,\n skipped: boolean,\n): UseQueryResult<T> {\n if (skipped) {\n return { data: undefined, loading: false, error: undefined };\n }\n return {\n data: value,\n loading: value === undefined,\n error: undefined,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Filters + options\n// ---------------------------------------------------------------------------\n\nexport interface UseConversationsFilters {\n /**\n * Truth user id (the Better Auth subject). Pass `undefined` to skip\n * the query — useful when the auth session is still loading.\n */\n userId: string | null | undefined;\n /**\n * Optional search term — when non-empty, switches the hook from\n * `conversations:listForUser` to `conversations:searchForUser`, which\n * fans out two parallel Convex full-text patient searches\n * (firstName + lastName) and returns the matching conversations the\n * user can see, sorted by recency. Whitespace-only values are treated\n * as empty.\n */\n search?: string;\n /** Page size. Server caps at 100 by default. */\n limit?: number;\n}\n\nexport interface UseMessagesOptions {\n /** Page size. Server caps at 200 by default. */\n limit?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to a user's conversations — joined with their per-conversation\n * unread count, sorted by most recent message. Updates live as new\n * SMS / calls land in Convex and the webhook bumps `unreadCount`.\n *\n * Returns `{ data: undefined, loading: false }` while the auth session\n * resolves (`userId === undefined`); not an error, just a skip.\n */\nfunction useConversations(\n filters: UseConversationsFilters,\n): UseQueryResult<ConversationListItem[]> {\n const trimmedSearch = filters.search?.trim() ?? \"\";\n const isSearchMode = trimmedSearch.length > 0;\n const skipped = !filters.userId;\n\n const listResult = useQuery(\n conversationsListForUserRef as FunctionReference<\"query\">,\n skipped || isSearchMode\n ? SKIP\n : {\n userId: filters.userId as string,\n limit: filters.limit,\n },\n ) as ConversationListItem[] | undefined;\n\n const searchResult = useQuery(\n conversationsSearchForUserRef as FunctionReference<\"query\">,\n skipped || !isSearchMode\n ? SKIP\n : {\n userId: filters.userId as string,\n search: trimmedSearch,\n limit: filters.limit,\n },\n ) as ConversationListItem[] | undefined;\n\n return toResult(isSearchMode ? searchResult : listResult, skipped);\n}\n\n/**\n * Look up a single conversation by its normalized phonePair (sorted\n * digit-pair, e.g. `\"5125550123|6505551234\"`). Returns `null` if no\n * conversation exists yet for that pair.\n *\n * Use the `phonePairKey(patientPhone, providerPhone)` helper exposed by\n * the Truth Convex module to derive the key from raw phone strings.\n */\nfunction useConversationByPhonePair(\n phonePair: string | null | undefined,\n): UseQueryResult<ConversationRow | null> {\n const skipped = !phonePair;\n const result = useQuery(\n conversationsGetByPhonePairRef as FunctionReference<\"query\">,\n skipped ? SKIP : { phonePair: phonePair as string },\n ) as ConversationRow | null | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Subscribe to a paginated message stream for a single conversation.\n * Calls + SMS merged chronologically, newest-first. Live as the\n * kinesis consumer writes to `messageCalls` / `messageSms`.\n *\n * Pass the Convex `_id` of the conversation row (from\n * `useConversations` / `useConversationByPhonePair`) as\n * `conversationId`.\n */\nfunction useMessages(\n conversationId: string | null | undefined,\n options?: UseMessagesOptions,\n): UseQueryResult<ConversationMessageRow[]> {\n const skipped = !conversationId;\n const result = useQuery(\n conversationMessagesGetByConversationIdRef as FunctionReference<\"query\">,\n skipped\n ? SKIP\n : {\n conversationId: conversationId as string,\n limit: options?.limit,\n },\n ) as ConversationMessageRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Subscribe to the user's total unread message count across all\n * conversations. Backs the inbox tab badge in CommHub.\n *\n * Returns `0` when the underlying query resolves with no unread\n * messages, `undefined` while loading or when `userId` is missing.\n */\nfunction useUnreadCount(\n userId: string | null | undefined,\n): UseQueryResult<number> {\n const skipped = !userId;\n const result = useQuery(\n conversationsGetUnreadTotalForUserRef as FunctionReference<\"query\">,\n skipped ? SKIP : { userId: userId as string },\n ) as number | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Server-side aggregate of the user's unread total + count of\n * conversations with at least one unread. Optionally filtered by\n * `providerPhones` (e.g. selected offices) — the aggregate is\n * computed in Convex, no client-side fetch-and-filter.\n */\nfunction useUnreadAggregate(\n userId: string | null | undefined,\n options?: { providerPhones?: string[] },\n): UseQueryResult<{ totalMessages: number; conversationCount: number }> {\n const skipped = !userId;\n const phones = options?.providerPhones;\n const stablePhones = useMemoizedPhones(phones);\n const result = useQuery(\n conversationsGetUnreadAggregateForUserRef as FunctionReference<\"query\">,\n skipped\n ? SKIP\n : {\n userId: userId as string,\n providerPhones: stablePhones,\n },\n ) as { totalMessages: number; conversationCount: number } | undefined;\n return toResult(result, skipped);\n}\n\n// Memoize the phones array so a new reference each render doesn't\n// retrigger the Convex subscription. Sort to canonicalize identity.\nfunction useMemoizedPhones(phones: string[] | undefined): string[] | undefined {\n const key = phones ? [...phones].sort().join(\"|\") : \"\";\n return useMemo(\n () => (phones && phones.length ? [...phones].sort() : undefined),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [key],\n );\n}\n\n/**\n * Subscribe to all notes attached to a conversation, newest first.\n * Replaces the urql Hasura `useConversation_NotesSubscription` in\n * CommHub (Truth #730). Pass `null`/`undefined` to skip the query.\n */\nfunction useConversationNotes(\n conversationId: string | null | undefined,\n): UseQueryResult<ConversationNoteRow[]> {\n const skipped = !conversationId;\n const result = useQuery(\n conversationNotesListForConversationRef as FunctionReference<\"query\">,\n skipped ? SKIP : { conversationId: conversationId as string },\n ) as ConversationNoteRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Subscribe to all tasks attached to a conversation, newest first.\n * Replaces the urql Hasura `useConversation_TasksSubscription` in\n * CommHub (Truth #730).\n */\nfunction useConversationTasks(\n conversationId: string | null | undefined,\n): UseQueryResult<ConversationTaskRow[]> {\n const skipped = !conversationId;\n const result = useQuery(\n conversationTasksListForConversationRef as FunctionReference<\"query\">,\n skipped ? SKIP : { conversationId: conversationId as string },\n ) as ConversationTaskRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Same as `useConversationNotes` but keyed on the normalized\n * `(patient_phone | provider_phone)` pair — saves the caller from\n * chaining a separate `useConversationByPhonePair` call. Returns\n * `[]` if no conversation exists for the pair yet.\n */\nfunction useConversationNotesByPhonePair(\n phonePair: string | null | undefined,\n): UseQueryResult<ConversationNoteRow[]> {\n const skipped = !phonePair;\n const result = useQuery(\n conversationNotesListByPhonePairRef as FunctionReference<\"query\">,\n skipped ? SKIP : { phonePair: phonePair as string },\n ) as ConversationNoteRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\n/**\n * Same as `useConversationTasks` but keyed on phonePair.\n */\n/**\n * Subscribe to every conversation task where the caller is the\n * assignee or the author. Backs CommHub's \"My Tasks\" tab.\n */\nfunction useConversationTasksForUser(\n userId: string | null | undefined,\n options?: { limit?: number },\n): UseQueryResult<ConversationTaskForUserRow[]> {\n const skipped = !userId;\n const result = useQuery(\n conversationTasksListForUserRef as FunctionReference<\"query\">,\n skipped ? SKIP : { userId: userId as string, limit: options?.limit },\n ) as ConversationTaskForUserRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\nfunction useConversationTasksByPhonePair(\n phonePair: string | null | undefined,\n): UseQueryResult<ConversationTaskRow[]> {\n const skipped = !phonePair;\n const result = useQuery(\n conversationTasksListByPhonePairRef as FunctionReference<\"query\">,\n skipped ? SKIP : { phonePair: phonePair as string },\n ) as ConversationTaskRow[] | undefined;\n\n return toResult(result, skipped);\n}\n\nexport {\n useConversationByPhonePair,\n useConversationNotes,\n useConversationNotesByPhonePair,\n useConversations,\n useConversationTasks,\n useConversationTasksByPhonePair,\n useConversationTasksForUser,\n useMessages,\n useUnreadAggregate,\n useUnreadCount,\n};\n","/**\n * React hooks for Truth SDK — real-time Convex-backed data access.\n *\n * These hooks use Convex React subscriptions for live-updating data.\n * Must be used within a ConvexProvider (see TruthProvider).\n *\n * @example\n * ```tsx\n * import { usePatients, useAppointments } from '@hipnation-truth/sdk/react';\n *\n * function PatientList() {\n * const patients = usePatients({ limit: 20 });\n * return patients?.map(p => <div key={p._id}>{p.firstName} {p.lastName}</div>);\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport { useEffect } from \"react\";\n\n// ---------------------------------------------------------------------------\n// Convex function references\n// ---------------------------------------------------------------------------\n// We use string-based references to avoid importing the generated API\n// (which lives in the consuming app, not the SDK). Convex supports this\n// via the `api` module pattern, but for a published SDK we use\n// makeFunctionReference to stay decoupled from the app's codegen.\n\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\n\n// Patient query references\nconst patientsListRef = makeFunctionReference<\n \"query\",\n {\n search?: string;\n lastName?: string;\n cursor?: string;\n limit?: number;\n },\n unknown[]\n>(\"patients:list\");\n\nconst patientsGetRef = makeFunctionReference<\n \"query\",\n { id: string },\n unknown | null\n>(\"patients:get\");\n\nconst patientsByElationIdRef = makeFunctionReference<\n \"query\",\n { elationId: string },\n unknown | null\n>(\"patients:getByElationId\");\n\nconst patientsByHintIdRef = makeFunctionReference<\n \"query\",\n { hintId: string },\n unknown | null\n>(\"patients:getByHintId\");\n\n// Appointment query references\nconst appointmentsListRef = makeFunctionReference<\n \"query\",\n {\n patientId?: string;\n status?: string;\n startDate?: string;\n endDate?: string;\n limit?: number;\n },\n unknown[]\n>(\"appointments:list\");\n\nconst appointmentsGetRef = makeFunctionReference<\n \"query\",\n { id: string },\n unknown | null\n>(\"appointments:get\");\n\nconst appointmentsByElationIdRef = makeFunctionReference<\n \"query\",\n { elationId: string },\n unknown | null\n>(\"appointments:getByElationId\");\n\n// ---------------------------------------------------------------------------\n// Patient hooks\n// ---------------------------------------------------------------------------\n\ninterface UsePatientListOptions {\n search?: string;\n lastName?: string;\n limit?: number;\n}\n\n/**\n * Subscribe to a list of patients with optional search and filtering.\n * Returns undefined while loading, then an array of patients.\n */\nfunction usePatients(options?: UsePatientListOptions) {\n return useQuery(patientsListRef as FunctionReference<\"query\">, options ?? {});\n}\n\n/**\n * Subscribe to a single patient by Convex document ID.\n */\nfunction usePatient(id: string) {\n return useQuery(patientsGetRef as FunctionReference<\"query\">, { id });\n}\n\n/**\n * Subscribe to a patient by their Elation ID.\n */\nfunction usePatientByElationId(elationId: string) {\n return useQuery(patientsByElationIdRef as FunctionReference<\"query\">, {\n elationId,\n });\n}\n\n/**\n * Subscribe to a patient by their Hint ID.\n */\nfunction usePatientByHintId(hintId: string) {\n return useQuery(patientsByHintIdRef as FunctionReference<\"query\">, {\n hintId,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Appointment hooks\n// ---------------------------------------------------------------------------\n\ninterface UseAppointmentListOptions {\n patientId?: string;\n status?: string;\n startDate?: string;\n endDate?: string;\n limit?: number;\n}\n\n/**\n * Subscribe to a list of appointments with optional filtering.\n */\nfunction useAppointments(options?: UseAppointmentListOptions) {\n return useQuery(\n appointmentsListRef as FunctionReference<\"query\">,\n options ?? {},\n );\n}\n\n/**\n * Subscribe to a single appointment by Convex document ID.\n */\nfunction useAppointment(id: string) {\n return useQuery(appointmentsGetRef as FunctionReference<\"query\">, { id });\n}\n\n/**\n * Subscribe to an appointment by its Elation ID.\n */\nfunction useAppointmentByElationId(elationId: string) {\n return useQuery(appointmentsByElationIdRef as FunctionReference<\"query\">, {\n elationId,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Physician hooks\n// ---------------------------------------------------------------------------\n\ninterface Physician {\n _id: string;\n elationId: number;\n firstName?: string;\n lastName?: string;\n npi?: string;\n credentials?: string;\n specialties?: string[];\n practice?: number;\n email?: string;\n phone?: string;\n lastSyncedAt: string;\n}\n\nconst physiciansGetByElationIdsRef = makeFunctionReference<\n \"query\",\n { ids: number[] },\n Physician[]\n>(\"physicians:getByElationIds\");\n\nconst physiciansGetByElationIdRef = makeFunctionReference<\n \"query\",\n { id: number },\n Physician | null\n>(\"physicians:getByElationId\");\n\n/**\n * Resolve a batch of physicians by their Elation IDs. Returns an array\n * of physicians that exist in Convex; missing ids are silently dropped.\n *\n * Use this to resolve medication `prescribing_physician` / appointment\n * physician ids to display names, avoiding the per-physician HTTP\n * round-trip through Truth's Elation proxy.\n *\n * Pass `undefined` (or an empty array) to skip the query — the hook\n * returns `undefined` until you pass a populated list.\n */\nfunction usePhysiciansByElationIds(ids: number[] | undefined) {\n return useQuery(\n physiciansGetByElationIdsRef as FunctionReference<\"query\">,\n ids && ids.length > 0 ? { ids } : \"skip\",\n );\n}\n\n/**\n * Subscribe to a single physician by their Elation ID.\n */\nfunction usePhysicianByElationId(id: number | undefined) {\n return useQuery(\n physiciansGetByElationIdRef as FunctionReference<\"query\">,\n id !== undefined ? { id } : \"skip\",\n );\n}\n\n// ---------------------------------------------------------------------------\n// Patient medical hooks (medications, problems, allergies, appointments)\n// ---------------------------------------------------------------------------\n\nconst medicationsByPatientRef = makeFunctionReference<\n \"query\",\n { elationPatientId: number },\n unknown[]\n>(\"medicalRecords:getMedicationsByElationPatient\");\n\nconst problemsByPatientRef = makeFunctionReference<\n \"query\",\n { elationPatientId: number },\n unknown[]\n>(\"medicalRecords:getProblemsByElationPatient\");\n\nconst allergiesByPatientRef = makeFunctionReference<\n \"query\",\n { elationPatientId: number },\n unknown[]\n>(\"medicalRecords:getAllergiesByElationPatient\");\n\nconst appointmentsByPatientRef = makeFunctionReference<\n \"query\",\n { elationPatientId: number },\n unknown[]\n>(\"medicalRecords:getAppointmentsByElationPatient\");\n\ninterface UsePatientMedicalOptions {\n /**\n * Base URL of the Truth API. Required to trigger the background refresh\n * (e.g. `https://app.truth.communication-hub.com`). If omitted, the hook\n * only reads from Convex and does not refresh.\n */\n apiBaseUrl?: string;\n /** API key used for the refresh call. */\n apiKey?: string;\n /**\n * If true, suppress the background refresh. Useful when you know the\n * data was just refreshed by another component on the page.\n */\n skipRefresh?: boolean;\n}\n\n/**\n * Composite hook that returns a patient's medical records — medications,\n * problems, allergies, appointments — from the Convex cache.\n *\n * On mount (and when `elationId` changes) fires a background refresh\n * against Truth's `/api/patients/medical/refresh` endpoint so stale data\n * is pulled in without blocking render. Returns cached data immediately;\n * Convex subscription updates the UI when refresh completes.\n */\nfunction usePatientMedical(\n elationId: number | undefined,\n options?: UsePatientMedicalOptions,\n) {\n const medications = useQuery(\n medicationsByPatientRef as FunctionReference<\"query\">,\n elationId !== undefined ? { elationPatientId: elationId } : \"skip\",\n ) as unknown[] | undefined;\n\n const problems = useQuery(\n problemsByPatientRef as FunctionReference<\"query\">,\n elationId !== undefined ? { elationPatientId: elationId } : \"skip\",\n ) as unknown[] | undefined;\n\n const allergies = useQuery(\n allergiesByPatientRef as FunctionReference<\"query\">,\n elationId !== undefined ? { elationPatientId: elationId } : \"skip\",\n ) as unknown[] | undefined;\n\n const appointments = useQuery(\n appointmentsByPatientRef as FunctionReference<\"query\">,\n elationId !== undefined ? { elationPatientId: elationId } : \"skip\",\n ) as unknown[] | undefined;\n\n useEffect(() => {\n if (elationId === undefined || options?.skipRefresh) {\n return;\n }\n const apiBaseUrl = options?.apiBaseUrl;\n const apiKey = options?.apiKey;\n if (!apiBaseUrl || !apiKey) {\n return;\n }\n\n const controller = new AbortController();\n void fetch(`${apiBaseUrl}/api/patients/medical/refresh`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify({ elationId }),\n signal: controller.signal,\n }).catch(() => {\n /* background refresh — failures are non-fatal */\n });\n\n return () => controller.abort();\n }, [elationId, options?.apiBaseUrl, options?.apiKey, options?.skipRefresh]);\n\n return { medications, problems, allergies, appointments };\n}\n\n// ---------------------------------------------------------------------------\n// Patient basic hooks (Elation demographics + Hint memberships/account)\n// ---------------------------------------------------------------------------\n//\n// Replaces the CommHub `getPatientBasicDetails` Hasura Action. Reads\n// from Convex-cached rows (populated by the patients-backfill cron +\n// `/api/patients/basic/refresh` on-demand), falling back to an empty\n// state until the background refresh completes.\n\nconst elationPatientByIdRef = makeFunctionReference<\n \"query\",\n { elationId: number },\n unknown | null\n>(\"elationPatients:getByElationId\");\n\nconst hintPatientByIdRef = makeFunctionReference<\n \"query\",\n { hintId: string },\n unknown | null\n>(\"hintPatients:getByHintId\");\n\nconst pharmacyByNcpdpRef = makeFunctionReference<\n \"query\",\n { ncpdpId: string },\n unknown | null\n>(\"elationPharmacies:getByNcpdpId\");\n\nconst patientPhotoByIdRef = makeFunctionReference<\n \"query\",\n { elationPatientId: number },\n unknown | null\n>(\"elationPatientPhotos:getByElationPatientId\");\n\ninterface UsePatientBasicOptions {\n /** Truth API base URL used for the background refresh. */\n apiBaseUrl?: string;\n /** API key for the refresh call. */\n apiKey?: string;\n /** Suppress the background refresh. */\n skipRefresh?: boolean;\n}\n\ninterface UsePatientBasicResult {\n /**\n * Raw Elation patient payload (matches the shape Elation's\n * `/patients/{id}` returns — `first_name`, `last_name`, `phones`, etc).\n * Returns `undefined` while the cache miss is loading, `null` if the\n * patient isn't in Convex yet (first-open-after-backfill).\n */\n elationPatient: Record<string, unknown> | null | undefined;\n /**\n * Raw Hint patient payload (matches the shape Hint's\n * `/provider/patients/{id}` returns — `memberships`, `account`,\n * `phones`, etc).\n */\n hintPatient: Record<string, unknown> | null | undefined;\n /**\n * The full Convex row for Elation (includes `elationId`, `lastSyncedAt`,\n * `photoS3Key`, `preferredPharmacyNcpdpId`, etc). Use this when you\n * need the structured/typed fields rather than the raw Elation payload.\n */\n elationRow: Record<string, unknown> | null | undefined;\n /**\n * The full Convex row for Hint. Similar relationship to `hintPatient`.\n */\n hintRow: Record<string, unknown> | null | undefined;\n /** True while either cache miss is still pending. */\n loading: boolean;\n}\n\n/**\n * Composite hook returning a patient's basic details — Hint demographics\n * + memberships + account, Elation demographics + clinical metadata —\n * from the Convex cache.\n *\n * On mount (and when inputs change) fires a background refresh against\n * `/api/patients/basic/refresh` so stale rows get pulled fresh without\n * blocking render. Returns cached data immediately; Convex subscription\n * updates the UI when refresh completes.\n */\nfunction usePatientBasic(\n input: { hintId?: string; elationId?: number },\n options?: UsePatientBasicOptions,\n): UsePatientBasicResult {\n const elationRow = useQuery(\n elationPatientByIdRef as FunctionReference<\"query\">,\n input.elationId !== undefined ? { elationId: input.elationId } : \"skip\",\n ) as Record<string, unknown> | null | undefined;\n\n const hintRow = useQuery(\n hintPatientByIdRef as FunctionReference<\"query\">,\n input.hintId !== undefined ? { hintId: input.hintId } : \"skip\",\n ) as Record<string, unknown> | null | undefined;\n\n useEffect(() => {\n if (options?.skipRefresh) {\n return;\n }\n if (!input.hintId && input.elationId === undefined) {\n return;\n }\n const apiBaseUrl = options?.apiBaseUrl;\n const apiKey = options?.apiKey;\n if (!apiBaseUrl || !apiKey) {\n return;\n }\n\n const controller = new AbortController();\n void fetch(`${apiBaseUrl}/api/patients/basic/refresh`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify({\n hintId: input.hintId,\n elationId: input.elationId,\n }),\n signal: controller.signal,\n }).catch(() => {\n /* background refresh — failures are non-fatal */\n });\n\n return () => controller.abort();\n }, [\n input.hintId,\n input.elationId,\n options?.apiBaseUrl,\n options?.apiKey,\n options?.skipRefresh,\n ]);\n\n // Surface the raw payload under elationPatient/hintPatient so consumers\n // can keep using the exact Elation/Hint field names (first_name,\n // memberships, account.past_due_in_cents) with zero mapping.\n const elationPatient =\n elationRow === undefined\n ? undefined\n : elationRow === null\n ? null\n : ((elationRow.raw as Record<string, unknown>) ?? null);\n const hintPatient =\n hintRow === undefined\n ? undefined\n : hintRow === null\n ? null\n : ((hintRow.raw as Record<string, unknown>) ?? null);\n\n const elationLoading =\n input.elationId !== undefined && elationRow === undefined;\n const hintLoading = input.hintId !== undefined && hintRow === undefined;\n\n return {\n elationPatient,\n hintPatient,\n elationRow,\n hintRow,\n loading: elationLoading || hintLoading,\n };\n}\n\n/**\n * Read a shared pharmacy row by NCPDP id. Backs the pharmacy card in\n * the patient panel (from Elation's preferred_pharmacy reference).\n */\nfunction usePharmacyByNcpdpId(ncpdpId: string | undefined) {\n return useQuery(\n pharmacyByNcpdpRef as FunctionReference<\"query\">,\n ncpdpId ? { ncpdpId } : \"skip\",\n );\n}\n\ninterface UsePatientPhotoOptions {\n apiBaseUrl?: string;\n apiKey?: string;\n skipRefresh?: boolean;\n}\n\n/**\n * Subscribe to a patient's profile photo (s3Key + metadata). The\n * consumer constructs a download URL via the Truth attachments resource\n * (signed S3 URL). Fires a background refresh to pull the latest photo\n * binary from Elation + upload to S3.\n */\nfunction usePatientPhoto(\n elationId: number | undefined,\n options?: UsePatientPhotoOptions,\n) {\n const photo = useQuery(\n patientPhotoByIdRef as FunctionReference<\"query\">,\n elationId !== undefined ? { elationPatientId: elationId } : \"skip\",\n );\n\n useEffect(() => {\n if (options?.skipRefresh) {\n return;\n }\n if (elationId === undefined) {\n return;\n }\n const apiBaseUrl = options?.apiBaseUrl;\n const apiKey = options?.apiKey;\n if (!apiBaseUrl || !apiKey) {\n return;\n }\n\n const controller = new AbortController();\n void fetch(`${apiBaseUrl}/api/patients/photo/refresh`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify({ elationId }),\n signal: controller.signal,\n }).catch(() => {\n /* non-fatal */\n });\n\n return () => controller.abort();\n }, [elationId, options?.apiBaseUrl, options?.apiKey, options?.skipRefresh]);\n\n return photo;\n}\n\n// ---------------------------------------------------------------------------\n// Conversation messages (Dialpad calls + SMS merged)\n// ---------------------------------------------------------------------------\n\ninterface ConversationMessage {\n kind: \"call\" | \"sms\";\n id: string;\n providerId: string;\n state: string | null;\n direction: string | null;\n fromNumber: string | null;\n toNumber: string | null;\n voicemailLink: string | null;\n duration: number | null;\n text: string | null;\n mms: boolean;\n mmsUrl: string | null;\n messageStatus: string | null;\n occurredAt: string;\n conversationId: string | null;\n patientId: string | null;\n}\n\nconst messagesByPhonesRef = makeFunctionReference<\n \"query\",\n { phoneA: string; phoneB: string; limit?: number },\n ConversationMessage[]\n>(\"conversationMessages:getByPhones\");\n\nconst messagesByConversationIdRef = makeFunctionReference<\n \"query\",\n { conversationId: string; limit?: number },\n ConversationMessage[]\n>(\"conversationMessages:getByConversationId\");\n\ninterface UseConversationMessagesOptions {\n /** Max items to return (default 200). */\n limit?: number;\n}\n\n/**\n * Subscribe to a conversation's calls + SMS merged chronologically.\n * Pass the patient phone + the provider phone — Truth computes a\n * normalized pair key server-side so formatting differences don't\n * matter.\n *\n * Returns `undefined` while loading, then `ConversationMessage[]`\n * sorted newest-first. Updates live as new webhook events land in\n * Convex via the kinesis consumer.\n */\nfunction useConversationMessages(\n input: { phoneA?: string; phoneB?: string; conversationId?: string },\n options?: UseConversationMessagesOptions,\n): ConversationMessage[] | undefined {\n const hasPair = !!input.phoneA && !!input.phoneB;\n const byPair = useQuery(\n messagesByPhonesRef as FunctionReference<\"query\">,\n hasPair\n ? {\n phoneA: input.phoneA as string,\n phoneB: input.phoneB as string,\n limit: options?.limit,\n }\n : \"skip\",\n ) as ConversationMessage[] | undefined;\n\n const byConvo = useQuery(\n messagesByConversationIdRef as FunctionReference<\"query\">,\n !hasPair && input.conversationId\n ? { conversationId: input.conversationId, limit: options?.limit }\n : \"skip\",\n ) as ConversationMessage[] | undefined;\n\n return hasPair ? byPair : byConvo;\n}\n\nexport {\n usePatients,\n usePatient,\n usePatientByElationId,\n usePatientByHintId,\n useAppointments,\n useAppointment,\n useAppointmentByElationId,\n usePhysiciansByElationIds,\n usePhysicianByElationId,\n usePatientMedical,\n usePatientBasic,\n usePharmacyByNcpdpId,\n usePatientPhoto,\n useConversationMessages,\n};\nexport type { ConversationMessage, UseConversationMessagesOptions };\nexport type {\n UsePatientListOptions,\n UseAppointmentListOptions,\n UsePatientMedicalOptions,\n UsePatientBasicOptions,\n UsePatientBasicResult,\n UsePatientPhotoOptions,\n Physician,\n};\n","/**\n * useNotifications — Truth SDK React hook for push notifications.\n *\n * Shape-compatible with `expo-notifications` where practical so\n * consumers port with minimal change. The hook dynamically imports\n * `expo-notifications` so the SDK doesn't hard-depend on Expo —\n * consumers running on a non-Expo runtime (e.g. a server test harness)\n * can still import the SDK without Metro blowing up.\n *\n * Exposes:\n * - permissionStatus — \"granted\" / \"denied\" / \"undetermined\"\n * - devicePushToken — native APNs/FCM token (undefined until register)\n * - register() — request permission, fetch token, register with Truth\n * - unregister() — revoke the device\n * - addReceivedListener / addResponseListener — re-exports of expo's\n * - getBadgeCount / setBadgeCount — re-exports of expo's\n *\n * Web push (VAPID subscription) lands in Phase 3.\n */\n\n\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n isWebPushSupported,\n registerServiceWorker,\n subscribeToPush,\n subscriptionToJSON,\n} from \"../web-push\";\n\n// `expo-notifications` is an optional peer dep. We lazy-import it via\n// a string-indirection so TypeScript + tsup don't try to resolve its\n// types at build time (the SDK ships without a hard dep so non-Expo\n// consumers — web, Node services — can still import from @hipnation-truth/sdk/react).\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ExpoNotificationsModule = any;\n\nasync function loadExpo(): Promise<ExpoNotificationsModule | null> {\n // Hermes rejects dynamic `import()`, so use Metro's `require()` which\n // is provided in every React Native module's scope. `expo-notifications`\n // is an optional peer dep — non-Expo consumers (web, Node) hit the\n // catch and we return null. tsup keeps this as a literal `require()`\n // (rather than its `__require` shim) because expo-notifications is\n // listed in `external` in tsup.config.ts — Metro's static analyzer\n // needs the literal call site to bundle the module.\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n return require(\"expo-notifications\") as ExpoNotificationsModule;\n } catch {\n return null;\n }\n}\n\nexport type PermissionStatus =\n | \"granted\"\n | \"denied\"\n | \"undetermined\"\n | \"unknown\";\n\nexport interface UseNotificationsOptions {\n /** Truth API base URL, e.g. https://app.truth.communication-hub.com */\n apiBaseUrl: string;\n /** `hn_live_*` API key for the caller's application. */\n apiKey: string;\n /** Current user id — used when registering the device. */\n userId: string | null | undefined;\n /** Optional app version string stored on the device row. */\n appVersion?: string;\n /**\n * Auto-register on mount when permission is already granted.\n * Default: true. Set false if you want to control the registration\n * lifecycle yourself.\n */\n autoRegister?: boolean;\n /** VAPID public key for web push. Fetched automatically if omitted. */\n vapidPublicKey?: string;\n /** Path to the service worker file. Default: \"/truth-sw.js\" */\n serviceWorkerPath?: string;\n /**\n * iOS only — which APNs endpoint the device's token will be valid\n * for. Determined by the build's `aps-environment` entitlement\n * (development ⇒ sandbox, production ⇒ production). Detect at the\n * call site via\n * `Application.getIosPushNotificationServiceEnvironmentAsync()`\n * from `expo-application` and pass the normalised value here. The\n * SDK can't read entitlements itself without taking on a native\n * peer dep. Server falls back to the app-level default if omitted.\n */\n apnsEnvironment?: \"sandbox\" | \"production\";\n}\n\nexport interface UseNotificationsResult {\n permissionStatus: PermissionStatus;\n devicePushToken: string | null;\n register: () => Promise<{ ok: boolean; reason?: string }>;\n unregister: () => Promise<void>;\n addReceivedListener: (listener: (n: unknown) => void) => () => void;\n addResponseListener: (listener: (r: unknown) => void) => () => void;\n getBadgeCount: () => Promise<number>;\n setBadgeCount: (count: number) => Promise<void>;\n}\n\nexport function useNotifications(\n options: UseNotificationsOptions,\n): UseNotificationsResult {\n const [permissionStatus, setPermissionStatus] =\n useState<PermissionStatus>(\"unknown\");\n const [devicePushToken, setDevicePushToken] = useState<string | null>(null);\n const expoRef = useRef<ExpoNotificationsModule | null>(null);\n const isWebRef = useRef(false);\n const vapidKeyRef = useRef<string | null>(options.vapidPublicKey ?? null);\n\n useEffect(() => {\n let mounted = true;\n void (async () => {\n const expo = await loadExpo();\n if (!mounted) {\n return;\n }\n expoRef.current = expo;\n\n if (expo) {\n try {\n const perm = await expo.getPermissionsAsync();\n if (!mounted) {\n return;\n }\n setPermissionStatus(mapStatus(perm?.status));\n } catch {\n setPermissionStatus(\"unknown\");\n }\n return;\n }\n\n if (isWebPushSupported()) {\n isWebRef.current = true;\n if (!vapidKeyRef.current) {\n try {\n const res = await fetch(\n `${options.apiBaseUrl}/api/notifications/vapid-key`,\n {\n headers: {\n Accept: \"application/json\",\n \"X-API-Key\": options.apiKey,\n },\n },\n );\n if (res.ok) {\n const data = await res.json();\n vapidKeyRef.current = data.vapidPublicKey ?? null;\n }\n } catch {\n // best-effort\n }\n }\n if (!mounted) {\n return;\n }\n const webPerm =\n typeof Notification !== \"undefined\"\n ? Notification.permission\n : \"default\";\n setPermissionStatus(\n webPerm === \"granted\"\n ? \"granted\"\n : webPerm === \"denied\"\n ? \"denied\"\n : \"undetermined\",\n );\n } else {\n setPermissionStatus(\"unknown\");\n }\n })();\n return () => {\n mounted = false;\n };\n }, [options.apiBaseUrl, options.apiKey]);\n\n const register = useCallback(async () => {\n if (!options.userId) {\n return { ok: false, reason: \"missing_userId\" };\n }\n\n // Web push path\n if (isWebRef.current) {\n const vapidKey = vapidKeyRef.current;\n if (!vapidKey) {\n return { ok: false, reason: \"no_vapid_key\" };\n }\n\n const webPerm = await Notification.requestPermission();\n setPermissionStatus(\n webPerm === \"granted\"\n ? \"granted\"\n : webPerm === \"denied\"\n ? \"denied\"\n : \"undetermined\",\n );\n if (webPerm !== \"granted\") {\n return { ok: false, reason: \"permission_denied\" };\n }\n\n try {\n const swPath = options.serviceWorkerPath ?? \"/truth-sw.js\";\n const registration = await registerServiceWorker(swPath);\n await navigator.serviceWorker.ready;\n const subscription = await subscribeToPush(registration, vapidKey);\n const subJSON = subscriptionToJSON(subscription);\n setDevicePushToken(subscription.endpoint);\n\n const res = await fetch(\n `${options.apiBaseUrl}/api/notifications/devices/register`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": options.apiKey,\n },\n body: JSON.stringify({\n userId: options.userId,\n platform: \"web\",\n webPushSubscription: subJSON,\n appVersion: options.appVersion,\n locale: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n }),\n },\n );\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n return {\n ok: false,\n reason: `register_failed_${res.status}: ${text.slice(0, 120)}`,\n };\n }\n return { ok: true };\n } catch (err) {\n return {\n ok: false,\n reason: `web_push_error: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n\n // Native (expo-notifications) path\n const expo = expoRef.current ?? (await loadExpo());\n expoRef.current = expo;\n if (!expo) {\n return { ok: false, reason: \"expo_notifications_missing\" };\n }\n\n let perm = await expo.getPermissionsAsync();\n if (perm?.status !== \"granted\") {\n perm = await expo.requestPermissionsAsync();\n }\n setPermissionStatus(mapStatus(perm?.status));\n if (perm?.status !== \"granted\") {\n return { ok: false, reason: \"permission_denied\" };\n }\n\n const tokenResp = await expo.getDevicePushTokenAsync();\n const nativeToken = tokenResp?.data;\n const platform = detectPlatform(tokenResp?.type);\n if (!nativeToken || (platform !== \"ios\" && platform !== \"android\")) {\n return { ok: false, reason: \"no_native_token\" };\n }\n\n setDevicePushToken(nativeToken);\n\n const res = await fetch(\n `${options.apiBaseUrl}/api/notifications/devices/register`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": options.apiKey,\n },\n body: JSON.stringify({\n userId: options.userId,\n platform,\n nativeToken,\n appVersion: options.appVersion,\n locale:\n typeof navigator !== \"undefined\" ? navigator.language : undefined,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n ...(platform === \"ios\" && options.apnsEnvironment\n ? { apnsEnvironment: options.apnsEnvironment }\n : {}),\n }),\n },\n );\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n return {\n ok: false,\n reason: `register_failed_${res.status}: ${text.slice(0, 120)}`,\n };\n }\n return { ok: true };\n }, [\n options.apiBaseUrl,\n options.apiKey,\n options.userId,\n options.appVersion,\n options.serviceWorkerPath,\n ]);\n\n const unregister = useCallback(async () => {\n if (!devicePushToken) {\n return;\n }\n await fetch(`${options.apiBaseUrl}/api/notifications/devices/unregister`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": options.apiKey,\n },\n body: JSON.stringify({ nativeToken: devicePushToken }),\n }).catch(() => {\n /* non-fatal */\n });\n setDevicePushToken(null);\n }, [options.apiBaseUrl, options.apiKey, devicePushToken]);\n\n const addReceivedListener = useCallback(\n (listener: (n: unknown) => void): (() => void) => {\n if (isWebRef.current) {\n if (\n typeof navigator === \"undefined\" ||\n !(\"serviceWorker\" in navigator)\n ) {\n return () => {};\n }\n const handler = (event: MessageEvent) => {\n if (event.data?.type === \"TRUTH_PUSH_RECEIVED\") {\n listener(event.data.payload);\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n return () =>\n navigator.serviceWorker.removeEventListener(\"message\", handler);\n }\n const expo = expoRef.current;\n if (!expo?.addNotificationReceivedListener) {\n return () => {};\n }\n const sub = expo.addNotificationReceivedListener(listener);\n return () => sub.remove?.();\n },\n [],\n );\n\n const addResponseListener = useCallback(\n (listener: (r: unknown) => void): (() => void) => {\n if (isWebRef.current) {\n if (\n typeof navigator === \"undefined\" ||\n !(\"serviceWorker\" in navigator)\n ) {\n return () => {};\n }\n const handler = (event: MessageEvent) => {\n if (event.data?.type === \"TRUTH_PUSH_TAPPED\") {\n listener(event.data.payload);\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n return () =>\n navigator.serviceWorker.removeEventListener(\"message\", handler);\n }\n const expo = expoRef.current;\n if (!expo?.addNotificationResponseReceivedListener) {\n return () => {};\n }\n const sub = expo.addNotificationResponseReceivedListener(listener);\n return () => sub.remove?.();\n },\n [],\n );\n\n const getBadgeCount = useCallback(async (): Promise<number> => {\n const expo = expoRef.current;\n if (!expo?.getBadgeCountAsync) {\n return 0;\n }\n return (await expo.getBadgeCountAsync()) ?? 0;\n }, []);\n\n const setBadgeCount = useCallback(async (count: number): Promise<void> => {\n const expo = expoRef.current;\n if (!expo?.setBadgeCountAsync) {\n return;\n }\n await expo.setBadgeCountAsync(count);\n }, []);\n\n // Auto-register once on mount when conditions are met\n const autoRegister = options.autoRegister !== false;\n useEffect(() => {\n if (!autoRegister) {\n return;\n }\n if (permissionStatus !== \"granted\") {\n return;\n }\n if (devicePushToken) {\n return;\n }\n if (!options.userId) {\n return;\n }\n void register();\n }, [\n autoRegister,\n permissionStatus,\n devicePushToken,\n options.userId,\n register,\n ]);\n\n return {\n permissionStatus,\n devicePushToken,\n register,\n unregister,\n addReceivedListener,\n addResponseListener,\n getBadgeCount,\n setBadgeCount,\n };\n}\n\nfunction mapStatus(status: string | undefined): PermissionStatus {\n if (status === \"granted\") {\n return \"granted\";\n }\n if (status === \"denied\") {\n return \"denied\";\n }\n if (status === \"undetermined\") {\n return \"undetermined\";\n }\n return \"unknown\";\n}\n\nfunction detectPlatform(\n tokenType: string | undefined,\n): \"ios\" | \"android\" | \"web\" | \"unknown\" {\n // expo-notifications >= 0.27 returns \"ios\" / \"android\" / \"web\"\n // directly. Older releases (and Firebase docs) used \"apns\" / \"fcm\";\n // accept both for back-compat with apps still on old SDKs.\n if (tokenType === \"ios\" || tokenType === \"apns\") {\n return \"ios\";\n }\n if (tokenType === \"android\" || tokenType === \"fcm\") {\n return \"android\";\n }\n if (tokenType === \"web\") {\n return \"web\";\n }\n return \"unknown\";\n}\n","/**\n * Web Push helpers for browser environments.\n *\n * Handles service worker registration, VAPID push subscription, and\n * message forwarding from the service worker to the main thread.\n */\n\nexport interface WebPushConfig {\n vapidPublicKey: string;\n serviceWorkerPath?: string;\n}\n\nexport function isWebPushSupported(): boolean {\n return (\n typeof window !== \"undefined\" &&\n \"serviceWorker\" in navigator &&\n \"PushManager\" in window\n );\n}\n\nexport async function registerServiceWorker(\n path = \"/truth-sw.js\",\n): Promise<ServiceWorkerRegistration> {\n return navigator.serviceWorker.register(path);\n}\n\nexport async function subscribeToPush(\n registration: ServiceWorkerRegistration,\n vapidPublicKey: string,\n): Promise<PushSubscription> {\n const existing = await registration.pushManager.getSubscription();\n if (existing) {\n return existing;\n }\n\n return registration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: urlBase64ToUint8Array(\n vapidPublicKey,\n ) as unknown as ArrayBuffer,\n });\n}\n\nexport function subscriptionToJSON(sub: PushSubscription): {\n endpoint: string;\n keys: { p256dh: string; auth: string };\n} {\n const json = sub.toJSON();\n return {\n endpoint: sub.endpoint,\n keys: {\n p256dh: json.keys?.p256dh ?? \"\",\n auth: json.keys?.auth ?? \"\",\n },\n };\n}\n\nfunction urlBase64ToUint8Array(base64String: string): Uint8Array {\n const padding = \"=\".repeat((4 - (base64String.length % 4)) % 4);\n const base64 = (base64String + padding).replace(/-/g, \"+\").replace(/_/g, \"/\");\n const rawData = atob(base64);\n const outputArray = new Uint8Array(rawData.length);\n for (let i = 0; i < rawData.length; ++i) {\n outputArray[i] = rawData.charCodeAt(i);\n }\n return outputArray;\n}\n\nexport function onServiceWorkerMessage(\n type: string,\n callback: (payload: unknown) => void,\n): () => void {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return () => {};\n }\n const handler = (event: MessageEvent) => {\n if (event.data?.type === type) {\n callback(event.data.payload);\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n return () => navigator.serviceWorker.removeEventListener(\"message\", handler);\n}\n","/**\n * React hook for patient family-member lookup — Truth SDK.\n *\n * Replaces the CommHub Hasura `useFamilyMembersQuery` (backed by\n * `queries/family_members.graphql`). Queries the Convex\n * `patients:listFamilyMembers` function, which returns the set of\n * patients that share a `familyId` OR share at least one phone number\n * with the reference patient, excluding the reference patient themselves.\n *\n * Arguments mirror the Hasura `FamilyMembers` GraphQL query variables:\n * - `familyId` → `$familyId` (Hint family/account id)\n * - `phoneNumbers` → `$phoneNumbers` (patient's phone numbers)\n * - `excludeHintId` → `$excludePatientId` (skip the reference patient)\n *\n * Must be used within a `<TruthProvider />`.\n *\n * @example\n * ```tsx\n * import { usePatientFamilyMembers } from '@hipnation-truth/sdk/react/patient-family';\n *\n * function FamilyPanel({ hintId, familyId, phones }: Props) {\n * const { data: members, loading } = usePatientFamilyMembers({\n * familyId,\n * phoneNumbers: phones,\n * excludeHintId: hintId,\n * });\n * if (loading) return <Spinner />;\n * return members?.map((m) => <div key={m._id}>{m.firstName} {m.lastName}</div>);\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Shape of a single patient row returned by `patients:listFamilyMembers`.\n * Mirrors the fields CommHub's `FamilyMembersQuery` selected from Hasura:\n * - `id` → `_id` (Convex document id)\n * - `name` → `firstName + \" \" + lastName`\n * - `hint_id` → `hintId`\n * - `elation_id` → `elationId`\n * - `family_id` → `familyId`\n * - `phone_numbers` → `phones[].number`\n */\nexport interface FamilyMemberRow {\n _id: string;\n _creationTime: number;\n firstName: string;\n lastName: string;\n elationId?: string;\n hintId?: string;\n familyId?: string;\n phones: Array<{ type?: string; number: string }>;\n membershipStatus?: string;\n sources: string[];\n lastSyncedAt: string;\n}\n\n/** Arguments for `usePatientFamilyMembers`. */\nexport interface UsePatientFamilyMembersInput {\n /**\n * Hint family/account id — when present, all patients sharing this id\n * are returned (matches the Hasura `family_id` column).\n *\n * BACKFILL NEEDED: `familyId` was added to the Convex `patients` schema\n * in this PR. Existing patient rows will return empty for this branch\n * until the Hint patient upsert path (upsertFromHint / basic-refresh)\n * is updated to write this field, or a one-time backfill script runs.\n * The phone-number fallback continues to work in the interim.\n */\n familyId?: string | null;\n /**\n * Patient's phone numbers — patients sharing at least one phone number\n * are included as family members (mirrors Hasura `phone_numbers._overlap`).\n */\n phoneNumbers?: string[] | null;\n /**\n * Hint patient ID of the reference patient to exclude from results\n * (mirrors Hasura `$excludePatientId`).\n */\n excludeHintId?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Convex function reference\n// ---------------------------------------------------------------------------\n\nconst patientsFamilyMembersRef = makeFunctionReference<\n \"query\",\n {\n familyId?: string;\n phoneNumbers?: string[];\n excludeHintId?: string;\n },\n FamilyMemberRow[]\n>(\"patients:listFamilyMembers\");\n\n// ---------------------------------------------------------------------------\n// Skip sentinel\n// ---------------------------------------------------------------------------\n\nconst SKIP = \"skip\" as const;\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to family members of a patient in real time.\n *\n * Returns all patients that share the same `familyId` OR share at least one\n * phone number with the reference patient, sorted by name. The reference\n * patient is excluded via `excludeHintId`.\n *\n * Pass `undefined` (or an object where all fields are undefined/null) to\n * skip the query — returns `{ data: undefined, loading: false }`.\n *\n * @param input - Query inputs. The query is skipped if `input` is undefined\n * or all fields are falsy.\n */\nfunction usePatientFamilyMembers(\n input: UsePatientFamilyMembersInput | undefined,\n): UseQueryResult<FamilyMemberRow[]> {\n const hasFamilyId = !!input?.familyId;\n const hasPhoneNumbers = !!(input?.phoneNumbers && input.phoneNumbers.length > 0);\n const shouldQuery = hasFamilyId || hasPhoneNumbers;\n\n const args = shouldQuery\n ? {\n ...(input?.familyId ? { familyId: input.familyId } : {}),\n ...(input?.phoneNumbers && input.phoneNumbers.length > 0\n ? { phoneNumbers: input.phoneNumbers }\n : {}),\n ...(input?.excludeHintId ? { excludeHintId: input.excludeHintId } : {}),\n }\n : SKIP;\n\n const result = useQuery(\n patientsFamilyMembersRef as FunctionReference<\"query\">,\n args,\n ) as FamilyMemberRow[] | undefined;\n\n if (!shouldQuery) {\n return { data: undefined, loading: false, error: undefined };\n }\n\n return {\n data: result,\n loading: result === undefined,\n error: undefined,\n };\n}\n\nexport { usePatientFamilyMembers };\n","/**\n * React hook for Truth SDK — reactive patient search.\n *\n * Wraps the `patients:search` Convex query so CommHub consumers\n * (and other frontends) can search patients by name, phone, or email\n * with live-updating results and optional office scoping, without\n * managing Convex subscriptions themselves.\n *\n * **Hook contract:** returns `UseQueryResult<PatientSearchResult[]>`.\n * - `data` is `undefined` while loading or when the query is skipped\n * (empty `query` string or `query === undefined`).\n * - `loading` is `true` only while a real subscription is in-flight.\n * - `error` is reserved for SDK-side validation; Convex query errors\n * propagate as React errors and should be caught with an error\n * boundary.\n *\n * **Backed by `patients:search`** — added in agent-A-search.\n * Multi-word queries split on whitespace; every token must match at\n * least one of firstName / lastName / email / phone digits.\n *\n * Must be used within `<TruthProvider />` (see `./provider`).\n *\n * @example\n * ```tsx\n * import { usePatientSearch } from '@hipnation-truth/sdk/react/patient-search';\n *\n * function PatientPicker({ officeId }: { officeId?: string }) {\n * const [query, setQuery] = useState('');\n * const { data: patients, loading } = usePatientSearch({ query, officeId });\n * return (\n * <>\n * <input value={query} onChange={e => setQuery(e.target.value)} />\n * {loading && <Spinner />}\n * {patients?.map(p => <PatientRow key={p.id} patient={p} />)}\n * </>\n * );\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n\n// ---------------------------------------------------------------------------\n// Return shape\n// ---------------------------------------------------------------------------\n\n/**\n * A single patient result from `patients:search`.\n *\n * Fields are normalised across the `patients` + `hintPatients` tables\n * so callers get a flat, consistent object regardless of which source\n * contributed the match.\n */\nexport interface PatientSearchResult {\n /** Convex `_id` of the matching `patients` or `hintPatients` row. */\n id: string;\n /** Hint patient id — present when the row originates from Hint. */\n hintId: string | undefined;\n firstName: string;\n lastName: string;\n /** ISO date string (YYYY-MM-DD) — undefined when not available. */\n dob: string | undefined;\n /** Email addresses associated with the patient. */\n emails: string[];\n /**\n * Raw phone strings from the source table. Callers should\n * strip non-digits for dialling; the first entry is the primary\n * contact number.\n */\n phones: string[];\n /** Hint office id — present for Hint-sourced results. */\n officeId: string | undefined;\n /** Human-readable office name — present when officeId is set. */\n officeName: string | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface UsePatientSearchOptions {\n /**\n * The search term entered by the user. Multi-word queries are split\n * on whitespace and every token must match. Pass `\"\"` or `undefined`\n * to skip the query and get `{ data: undefined, loading: false }`.\n */\n query: string | undefined;\n /**\n * When supplied, restricts results to patients whose authoritative\n * Hint office id matches. Maps to the `officeId` arg on the Convex\n * query and uses the `hintPatients.by_officeId` index path.\n */\n officeId?: string | null;\n /**\n * `\"fuzzy\"` (default) uses Convex full-text search indexes for name\n * queries; `\"exact\"` uses a bounded scan-filter only. Most callers\n * should leave this at the default.\n */\n mode?: \"fuzzy\" | \"exact\";\n /** Maximum number of results to return (server caps at 200). */\n limit?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Convex function reference\n// ---------------------------------------------------------------------------\n\n// String-keyed reference to `patients:search` — decoupled from the\n// consuming app's generated `convex/_generated/api`.\nconst patientsSearchRef = makeFunctionReference<\n \"query\",\n {\n query: string;\n officeId?: string;\n limit?: number;\n mode?: \"fuzzy\" | \"exact\";\n },\n PatientSearchResult[]\n>(\"patients:search\");\n\n// ---------------------------------------------------------------------------\n// Skipped-query sentinel\n// ---------------------------------------------------------------------------\n\nconst SKIP = \"skip\" as const;\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to a live patient search backed by `patients:search` on the\n * Truth Convex deployment.\n *\n * Results update reactively whenever the underlying `patients` or\n * `hintPatients` tables change — no polling required.\n *\n * @param options - Search options (see `UsePatientSearchOptions`).\n * @returns `UseQueryResult<PatientSearchResult[]>` — always defined\n * once a non-empty `query` is provided and the subscription resolves.\n */\nexport function usePatientSearch(\n options: UsePatientSearchOptions,\n): UseQueryResult<PatientSearchResult[]> {\n const trimmedQuery = (options.query ?? \"\").trim();\n const skipped = trimmedQuery.length === 0;\n\n const result = useQuery(\n patientsSearchRef as FunctionReference<\"query\">,\n skipped\n ? SKIP\n : {\n query: trimmedQuery,\n ...(options.officeId ? { officeId: options.officeId } : {}),\n ...(options.mode ? { mode: options.mode } : {}),\n ...(options.limit !== undefined ? { limit: options.limit } : {}),\n },\n ) as PatientSearchResult[] | undefined;\n\n if (skipped) {\n return { data: undefined, loading: false, error: undefined };\n }\n\n return {\n data: result,\n loading: result === undefined,\n error: undefined,\n };\n}\n","/**\n * Bulk patient lookup by Convex ids.\n *\n * Backs the inbox card render in CommHub — one reactive subscription\n * resolves names + office + EHR ids for the entire visible list\n * instead of N per-row queries.\n *\n * Must be used within `<TruthProvider />`.\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport { useMemo } from \"react\";\nimport type { Patient } from \"../types/patient\";\nimport type { UseQueryResult } from \"./conversations\";\n\nconst patientsGetByIdsRef = makeFunctionReference<\n \"query\",\n { ids: string[] },\n Patient[]\n>(\"patients:getByIds\");\n\nconst patientsGetByPhonesRef = makeFunctionReference<\n \"query\",\n { phoneDigits: string[] },\n Array<{ phoneDigits: string; patient: Patient }>\n>(\"patients:getByPhones\");\n\nconst SKIP = \"skip\" as const;\n\n/**\n * Subscribe to a list of patients by their Convex ids. Returns a\n * `Record<patientId, Patient>` for O(1) lookup at render time.\n *\n * Pass an empty array (or `undefined`) to skip the query. Missing ids\n * are absent from the map (silently dropped server-side).\n */\nexport function usePatientsByIds(\n ids: string[] | null | undefined,\n): UseQueryResult<Record<string, Patient>> {\n const stableIds = useMemo(() => {\n const arr = ids ?? [];\n return [...new Set(arr)].sort();\n }, [ids]);\n const skipped = stableIds.length === 0;\n\n const result = useQuery(\n patientsGetByIdsRef as FunctionReference<\"query\">,\n skipped ? SKIP : { ids: stableIds },\n ) as Patient[] | undefined;\n\n const mapped = useMemo(() => {\n if (result === undefined) return undefined;\n return Object.fromEntries(\n result.map((p) => {\n const id =\n (p as { id?: string; _id?: string }).id ??\n (p as { _id?: string })._id ??\n \"\";\n return [String(id), p];\n }),\n );\n }, [result]);\n\n if (skipped) {\n return { data: {}, loading: false, error: undefined };\n }\n return {\n data: mapped,\n loading: mapped === undefined,\n error: undefined,\n };\n}\n\n/**\n * Bulk patient lookup by phone numbers (digits or formatted). Returns\n * `Record<phoneDigits, Patient>` for O(1) lookup by digits-only key.\n */\nexport function usePatientsByPhones(\n phones: string[] | null | undefined,\n): UseQueryResult<Record<string, Patient>> {\n const stableDigits = useMemo(() => {\n const arr = phones ?? [];\n const digits = arr\n .map((p) => p.replace(/\\D+/g, \"\"))\n .filter((s) => s.length > 0);\n return [...new Set(digits)].sort();\n }, [phones]);\n const skipped = stableDigits.length === 0;\n\n const result = useQuery(\n patientsGetByPhonesRef as FunctionReference<\"query\">,\n skipped ? SKIP : { phoneDigits: stableDigits },\n ) as Array<{ phoneDigits: string; patient: Patient }> | undefined;\n\n const mapped = useMemo(() => {\n if (result === undefined) return undefined;\n return Object.fromEntries(result.map((r) => [r.phoneDigits, r.patient]));\n }, [result]);\n\n if (skipped) {\n return { data: {}, loading: false, error: undefined };\n }\n return {\n data: mapped,\n loading: mapped === undefined,\n error: undefined,\n };\n}\n","/**\n * TruthProvider — Convex React provider pre-configured for Truth.\n *\n * Wraps children in a ConvexProvider connected to the correct\n * Truth Convex deployment for the given environment.\n *\n * @example\n * ```tsx\n * import { TruthProvider } from '@hipnation-truth/sdk/react';\n *\n * function App() {\n * return (\n * <TruthProvider environment=\"sandbox\">\n * <MyApp />\n * </TruthProvider>\n * );\n * }\n * ```\n */\n\n\"use client\";\n\nimport { ConvexProvider, ConvexReactClient } from \"convex/react\";\nimport type { ReactNode } from \"react\";\nimport { createElement, useMemo } from \"react\";\n\n// Mirrors CONVEX_URLS in client.ts. UAT shares prod resources.\n//\n// Exported so `resolveConvexUrl` can be unit-tested without booting\n// React — a regression in this map shipped SDK 0.4.1 that pointed\n// UAT at sandbox Convex. The test in provider.test.ts locks every\n// stage → URL pair down.\nexport const CONVEX_URLS: Record<string, string> = {\n local: \"https://courteous-duck-623.convex.cloud\",\n staging: \"https://courteous-duck-623.convex.cloud\",\n stg: \"https://courteous-duck-623.convex.cloud\",\n sandbox: \"https://courteous-duck-623.convex.cloud\",\n uat: \"https://gallant-gecko-217.convex.cloud\",\n production: \"https://gallant-gecko-217.convex.cloud\",\n};\n\n/**\n * Resolve the Convex URL for a given environment. Honors an explicit\n * override and falls back to sandbox for unknown environments.\n */\nexport function resolveConvexUrl(\n environment: string | undefined,\n override?: string,\n): string {\n if (override) {\n return override;\n }\n const env = environment ?? \"sandbox\";\n return CONVEX_URLS[env] ?? CONVEX_URLS.sandbox;\n}\n\ninterface TruthProviderProps {\n /** Truth environment — determines which Convex deployment to connect to */\n environment?: string;\n /** Override the Convex URL directly */\n convexUrl?: string;\n children: ReactNode;\n}\n\nfunction TruthProvider({\n environment = \"sandbox\",\n convexUrl,\n children,\n}: TruthProviderProps) {\n const url = resolveConvexUrl(environment, convexUrl);\n\n const client = useMemo(() => new ConvexReactClient(url), [url]);\n\n return createElement(ConvexProvider, { client }, children);\n}\n\nexport { TruthProvider };\nexport type { TruthProviderProps };\n","/**\n * React hooks for conversation reminders — bulk lookup keyed by\n * conversation id. Wraps the public `reminders:listPendingByConversationIds`\n * Convex query so CommHub's inbox can render the reminder clock icon\n * with one reactive subscription for the entire visible list.\n *\n * Must be used within `<TruthProvider />` (see `./provider`).\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { Reminder } from \"../types/reminder\";\nimport type { UseQueryResult } from \"./conversations\";\n\nconst remindersListPendingByConversationIdsRef = makeFunctionReference<\n \"query\",\n { conversationIds: string[] },\n Reminder[]\n>(\"reminders:listPendingByConversationIds\");\n\nconst SKIP = \"skip\" as const;\n\n/**\n * Subscribe to the latest pending reminder for each of the given\n * conversation ids. Returns a `Record<conversationId, Reminder>` —\n * conversations with no pending reminder are absent from the map.\n *\n * Pass an empty array (or `undefined`) to skip the query — useful while\n * the inbox list is still loading.\n *\n * @example\n * ```tsx\n * const ids = conversations.map((c) => c.id);\n * const { data: remindersByConv } = useRemindersForConversations(ids);\n * const r = remindersByConv?.[conv.id]; // Reminder | undefined\n * ```\n */\nexport function useRemindersForConversations(\n conversationIds: string[] | null | undefined,\n): UseQueryResult<Record<string, Reminder>> {\n const ids = conversationIds ?? [];\n const skipped = ids.length === 0;\n\n const result = useQuery(\n remindersListPendingByConversationIdsRef as FunctionReference<\"query\">,\n skipped ? SKIP : { conversationIds: ids },\n ) as Reminder[] | undefined;\n\n const mapped =\n result === undefined\n ? undefined\n : Object.fromEntries(result.map((r) => [r.conversationId, r]));\n\n if (skipped) {\n return { data: {}, loading: false, error: undefined };\n }\n return {\n data: mapped,\n loading: mapped === undefined,\n error: undefined,\n };\n}\n","/**\n * React tracking hook — fire-and-forget event tracking from client components.\n *\n * Creates a lightweight Tracker instance scoped to the browser session\n * and exposes a useTrack() hook for emitting events to Truth's Kinesis stream.\n *\n * @example\n * ```tsx\n * import { useTruth } from '@hipnation-truth/sdk/react';\n *\n * function ConversationView({ conversationId }) {\n * const truth = useTruth();\n *\n * useEffect(() => {\n * truth.track('conversation.marked_read.v1', {\n * read_by_actor_id: userId,\n * unread_count_before: 5,\n * unread_count_after: 0,\n * }, { subject: { conversation_id: conversationId } });\n * }, [conversationId]);\n * }\n * ```\n */\n\n\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { createContext, createElement, useContext, useMemo } from \"react\";\nimport type {\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"../tracking/events\";\nimport { Tracker } from \"../tracking/tracker\";\nimport type { ActorContext } from \"../types/config\";\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface TruthTrackingContextValue {\n track: <T extends EventType>(\n eventType: T,\n payload: EventPayloadMap[T],\n options?: TrackOptions,\n ) => void;\n identify: (actorId: string, actorType: ActorContext[\"actorType\"]) => void;\n}\n\nconst TruthTrackingContext = createContext<TruthTrackingContextValue | null>(\n null,\n);\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\ninterface TruthTrackingProviderProps {\n /** Truth environment — determines API URL for event delivery */\n environment?: string;\n /** Event source identifier */\n source?: string;\n /** Source version (git SHA) */\n sourceVersion?: string;\n /** Default tenant ID */\n tenantId?: string;\n /** API key for authentication */\n apiKey?: string;\n children: ReactNode;\n}\n\nfunction TruthTrackingProvider({\n environment = \"sandbox\",\n source = \"communication-hub.frontend\",\n sourceVersion = \"unknown\",\n tenantId = \"hipnation\",\n apiKey = \"\",\n children,\n}: TruthTrackingProviderProps) {\n const value = useMemo<TruthTrackingContextValue>(() => {\n const tracker = new Tracker({\n apiKey,\n environment,\n source,\n sourceVersion,\n tenantId,\n batchSize: 10,\n flushIntervalMs: 5000,\n });\n\n return {\n track: (eventType, payload, options) => {\n tracker.track(eventType, payload, options);\n },\n identify: (actorId, actorType) => {\n tracker.setActor({ actorId, actorType });\n },\n };\n }, [apiKey, environment, source, sourceVersion, tenantId]);\n\n return createElement(TruthTrackingContext.Provider, { value }, children);\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Access the Truth tracking context. Must be within a TruthTrackingProvider.\n * Returns `{ track, identify }` for emitting events to Kinesis.\n */\nfunction useTruth(): TruthTrackingContextValue {\n const ctx = useContext(TruthTrackingContext);\n if (!ctx) {\n throw new Error(\"useTruth must be used within a TruthTrackingProvider\");\n }\n return ctx;\n}\n\nexport { TruthTrackingProvider, useTruth };\nexport type { TruthTrackingProviderProps, TruthTrackingContextValue };\n","/**\n * Event tracker with batching, retry, and flush support.\n *\n * Buffers events in an internal queue and flushes them to the Truth API\n * endpoint. Flushes occur when the buffer reaches `batchSize` or every\n * `flushIntervalMs` milliseconds, whichever comes first.\n */\n\nimport type { ActorContext } from \"../types/config\";\nimport type {\n EventEnvelope,\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"./events\";\n\n// ---------------------------------------------------------------------------\n// UUID v7 helper (no external dependency)\n// ---------------------------------------------------------------------------\n\n/**\n * Generates a UUID v7 string. Uses crypto.randomUUID where available,\n * falling back to a timestamp + random bytes implementation.\n *\n * UUID v7 layout (RFC 9562):\n * 48 bits - unix timestamp (ms)\n * 4 bits - version (0b0111)\n * 12 bits - random\n * 2 bits - variant (0b10)\n * 62 bits - random\n */\nfunction generateUuidV7(): string {\n const now = Date.now();\n\n // 6 bytes of timestamp\n const timeBytes = new Uint8Array(6);\n let ts = now;\n for (let i = 5; i >= 0; i--) {\n timeBytes[i] = ts & 0xff;\n ts = Math.floor(ts / 256);\n }\n\n // 10 bytes of random\n const randomBytes = new Uint8Array(10);\n if (\n typeof globalThis.crypto !== \"undefined\" &&\n globalThis.crypto.getRandomValues\n ) {\n globalThis.crypto.getRandomValues(randomBytes);\n } else {\n for (let i = 0; i < 10; i++) {\n randomBytes[i] = Math.floor(Math.random() * 256);\n }\n }\n\n // Assemble 16 bytes\n const bytes = new Uint8Array(16);\n bytes.set(timeBytes, 0);\n bytes.set(randomBytes, 6);\n\n // Set version (bits 48-51 to 0b0111)\n bytes[6] = (bytes[6] & 0x0f) | 0x70;\n\n // Set variant (bits 64-65 to 0b10)\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n\n // Format as hex string with dashes\n const hex = Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20, 32),\n ].join(\"-\");\n}\n\n// ---------------------------------------------------------------------------\n// Environment-based API URL resolution\n// ---------------------------------------------------------------------------\n\n// Environment → Truth API base URL.\n//\n// Topology:\n// local / staging / stg / sandbox → sandbox Truth (app.sandbox.*)\n// uat / production → production Truth (app.truth.*)\n//\n// UAT shares production resources (same Fly Postgres + Hasura + Truth +\n// Convex + EHR tokens as prod). Staging is the isolated sandbox env.\nconst API_URLS: Record<string, string> = {\n local: \"http://localhost:3000\",\n staging: \"https://app.sandbox.communication-hub.com\",\n stg: \"https://app.sandbox.communication-hub.com\",\n sandbox: \"https://app.sandbox.communication-hub.com\",\n uat: \"https://app.truth.communication-hub.com\",\n production: \"https://app.truth.communication-hub.com\",\n};\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_BATCH_SIZE = 25;\nconst DEFAULT_FLUSH_INTERVAL_MS = 5_000;\nconst MAX_RETRIES = 3;\nconst BASE_RETRY_DELAY_MS = 500;\n\n// ---------------------------------------------------------------------------\n// Tracker configuration\n// ---------------------------------------------------------------------------\n\ninterface TrackerConfig {\n apiKey: string;\n environment: string;\n source: string;\n sourceVersion: string;\n tenantId: string;\n batchSize: number;\n flushIntervalMs: number;\n apiBaseUrl?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Tracker class\n// ---------------------------------------------------------------------------\n\nclass Tracker {\n private readonly config: TrackerConfig;\n readonly apiUrl: string;\n private queue: EventEnvelope[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private defaultActor: ActorContext | undefined;\n private isFlushing = false;\n private isShutdown = false;\n\n constructor(config: TrackerConfig) {\n this.config = config;\n this.apiUrl =\n config.apiBaseUrl ?? API_URLS[config.environment] ?? API_URLS.local;\n\n this.startFlushInterval();\n this.registerShutdownHooks();\n }\n\n /**\n * Set the default actor context for subsequent events.\n */\n setActor(actor: ActorContext): void {\n this.defaultActor = actor;\n }\n\n /**\n * Enqueue a typed event for delivery. This is fire-and-forget from\n * the caller's perspective -- events are buffered and flushed in batches.\n */\n track<T extends EventType>(\n eventType: T,\n payload: EventPayloadMap[T],\n options?: TrackOptions,\n ): void {\n if (this.isShutdown) {\n return;\n }\n\n const now = new Date().toISOString();\n\n const envelope: EventEnvelope = {\n event_id: generateUuidV7(),\n event_type: eventType,\n schema_version: 1,\n occurred_at: options?.occurredAt ?? now,\n received_at: now,\n source: this.config.source,\n source_version: this.config.sourceVersion,\n tenant_id: options?.tenantId ?? this.config.tenantId,\n actor:\n options?.actor ??\n (this.defaultActor\n ? {\n actor_id: this.defaultActor.actorId,\n actor_type: this.defaultActor.actorType,\n }\n : undefined),\n subject: options?.subject,\n compliance: options?.compliance,\n payload: payload as unknown as Record<string, unknown>,\n };\n\n this.queue.push(envelope);\n\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Force an immediate flush of all buffered events.\n * Returns a promise that resolves when the flush completes.\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0 || this.isFlushing) {\n return;\n }\n\n this.isFlushing = true;\n const batch = this.queue.splice(0, this.config.batchSize);\n\n try {\n await this.sendBatch(batch);\n } catch {\n // Re-queue events that failed to send (prepend to maintain ordering)\n this.queue.unshift(...batch);\n } finally {\n this.isFlushing = false;\n }\n\n // If there are still events in the queue, flush again\n if (this.queue.length >= this.config.batchSize) {\n await this.flush();\n }\n }\n\n /**\n * Gracefully shut down the tracker. Flushes remaining events and\n * clears the flush interval.\n */\n async shutdown(): Promise<void> {\n this.isShutdown = true;\n this.stopFlushInterval();\n await this.flush();\n }\n\n /**\n * Send a batch of events to the Truth API with exponential backoff retry.\n */\n private async sendBatch(batch: EventEnvelope[]): Promise<void> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetch(`${this.apiUrl}/api/events/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.config.apiKey,\n },\n body: JSON.stringify({ events: batch }),\n });\n\n if (response.ok) {\n return;\n }\n\n // Don't retry 4xx client errors (except 429)\n if (\n response.status >= 400 &&\n response.status < 500 &&\n response.status !== 429\n ) {\n return;\n }\n\n lastError = new Error(\n `HTTP ${response.status}: ${response.statusText}`,\n );\n } catch (error) {\n lastError = error;\n }\n\n // Exponential backoff with jitter\n if (attempt < MAX_RETRIES) {\n const delay = BASE_RETRY_DELAY_MS * 2 ** attempt;\n const jitter = Math.random() * delay * 0.5;\n await sleep(delay + jitter);\n }\n }\n\n throw lastError;\n }\n\n private startFlushInterval(): void {\n if (this.config.flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n void this.flush();\n }, this.config.flushIntervalMs);\n\n // Unref the timer so it doesn't prevent Node.js from exiting\n if (typeof this.flushTimer === \"object\" && \"unref\" in this.flushTimer) {\n this.flushTimer.unref();\n }\n }\n }\n\n private stopFlushInterval(): void {\n if (this.flushTimer !== null) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n }\n\n private registerShutdownHooks(): void {\n // Only register in Node.js environments\n if (typeof globalThis.process !== \"undefined\" && globalThis.process.on) {\n const shutdownHandler = () => {\n void this.shutdown();\n };\n\n globalThis.process.on(\"beforeExit\", shutdownHandler);\n globalThis.process.on(\"SIGTERM\", shutdownHandler);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport {\n Tracker,\n generateUuidV7,\n DEFAULT_BATCH_SIZE,\n DEFAULT_FLUSH_INTERVAL_MS,\n};\nexport type { TrackerConfig };\n","/**\n * useUserSettings — reactive hook for per-user notification preferences\n * stored in the Truth `userSettings` Convex table.\n *\n * Must be used within `<TruthProvider />` (see `./provider`).\n *\n * @example\n * ```tsx\n * const { data: settings, loading } = useUserSettings(userId);\n * // settings?.notificationsEnabled — boolean or undefined (no row yet)\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\nimport type { UserSettings } from \"../resources/user-settings\";\n\n// ---------------------------------------------------------------------------\n// Convex function reference\n// ---------------------------------------------------------------------------\n\nconst userSettingsGetByUserIdRef = makeFunctionReference<\n \"query\",\n { userId: string },\n UserSettings | null\n>(\"userSettings:getByUserId\");\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nconst SKIP = \"skip\" as const;\n\n/**\n * Subscribe to the `userSettings` row for `userId`. Returns `null` when\n * no row exists (treat as all-defaults). Returns `undefined` while the\n * Convex query is in-flight.\n *\n * Pass `null` or `undefined` as `userId` to skip the query (e.g. while\n * auth is loading).\n */\nexport function useUserSettings(\n userId: string | null | undefined,\n): UseQueryResult<UserSettings | null> {\n const skip = !userId;\n\n const result = useQuery(\n userSettingsGetByUserIdRef as FunctionReference<\"query\">,\n skip ? SKIP : { userId: userId as string },\n ) as UserSettings | null | undefined;\n\n if (skip) {\n return { data: null, loading: false, error: undefined };\n }\n\n return {\n data: result ?? null,\n loading: result === undefined,\n error: undefined,\n };\n}\n","/**\n * React hook for fetching an authenticated Dialpad voicemail URL.\n *\n * Provides an imperative `fetchUrl` callback that wraps\n * `client.messages.getVoicemailUrl()` in React state so CommHub's\n * `AudioPlayer` can replace its urql `useMutation` with a single hook\n * call, without adding a query library dependency.\n *\n * The TruthClient instance is passed directly so the hook is testable\n * and doesn't require a global singleton or React context.\n *\n * @example\n * ```tsx\n * import { useVoicemailUrl } from '@hipnation-truth/sdk/react/voicemail';\n * import { getTruthClient } from '@/lib/truthClient';\n *\n * function AudioPlayer({ uri }: { uri: string }) {\n * const { fetchUrl, url, isLoading, error } = useVoicemailUrl(getTruthClient());\n *\n * const handlePlay = async () => {\n * const authenticated = await fetchUrl(uri);\n * if (authenticated) { ... }\n * };\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { TruthClient } from \"../client\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseVoicemailUrlResult {\n /** Trigger the URL fetch. Resolves with the clean playback URL or null on error. */\n fetchUrl: (voicemailLink: string) => Promise<string | null>;\n /** The most recently fetched URL (null until first successful fetch). */\n url: string | null;\n /** True while the fetch is in-flight. */\n isLoading: boolean;\n /** Error message from the last failed fetch, or null. */\n error: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Provides an imperative `fetchUrl` callback that authenticates a Dialpad\n * voicemail link via the Truth SDK `messages.getVoicemailUrl()` method.\n *\n * @param client The TruthClient instance (e.g., from `getTruthClient()`).\n */\nexport function useVoicemailUrl(client: TruthClient): UseVoicemailUrlResult {\n const [url, setUrl] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Guard against concurrent fetches (user double-tapping play).\n const inFlightRef = useRef(false);\n\n const fetchUrl = useCallback(\n async (voicemailLink: string): Promise<string | null> => {\n if (inFlightRef.current) return null;\n inFlightRef.current = true;\n setIsLoading(true);\n setError(null);\n\n try {\n const result = await client.messages.getVoicemailUrl(voicemailLink);\n setUrl(result.url);\n return result.url;\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown error\";\n setError(msg);\n return null;\n } finally {\n setIsLoading(false);\n inFlightRef.current = false;\n }\n },\n [client],\n );\n\n return { fetchUrl, url, isLoading, error };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACuBA,mBAAyB;AAEzB,oBAAsC;AAU/B,IAAM,mBAAmB;AAAA,EAC9B,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,mBAAmB;AACrB;AAKO,IAAM,sBAAqD,oBAAI,IAAI;AAAA,EACxE,iBAAiB;AAAA,EACjB,iBAAiB;AACnB,CAAC;AAGM,IAAM,wBAAuD,oBAAI,IAAI;AAAA,EAC1E,iBAAiB;AACnB,CAAC;AAQM,IAAM,qBAAoD,oBAAI,IAAI;AAAA,EACvE,GAAG;AAAA,EACH,GAAG;AACL,CAAC;AAGM,IAAM,uBAAsD,oBAAI,IAAI;AAAA,EACzE,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,iBAAiB;AACnB,CAAC;AAyCD,IAAM,oBAAgB,qCAIpB,8BAA8B;AAEhC,IAAM,6BAAyB,qCAI7B,uCAAuC;AAEzC,IAAM,qBAAiB,qCAIrB,+BAA+B;AAEjC,IAAM,0BAAsB,qCAI1B,oCAAoC;AAMtC,IAAM,OAAO;AAEb,SAAS,SACP,OACA,SACmB;AACnB,MAAI,SAAS;AACX,WAAO,EAAE,MAAM,QAAW,SAAS,OAAO,OAAO,OAAU;AAAA,EAC7D;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,UAAU;AAAA,IACnB,OAAO;AAAA,EACT;AACF;AAqBO,SAAS,eACd,SACkC;AAClC,QAAM,aAAS;AAAA,IACb;AAAA,IACA,4BAAW,CAAC;AAAA,EACd;AACA,SAAO,SAAS,QAAQ,KAAK;AAC/B;AAMO,SAAS,+BACd,gBACA,SACkC;AAClC,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UACI,OACA;AAAA,MACE;AAAA,MACA,OAAO,mCAAS;AAAA,IAClB;AAAA,EACN;AACA,SAAO,SAAS,QAAQ,OAAO;AACjC;AAGO,SAAS,uBACd,QACuC;AACvC,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAU,OAAO,EAAE,OAAyB;AAAA,EAC9C;AACA,SAAO,SAAS,QAAQ,OAAO;AACjC;AAMO,SAAS,kBACd,QACA,SACqC;AACrC,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAU,OAAO,EAAE,QAA0B,OAAO,mCAAS,MAAM;AAAA,EACrE;AACA,SAAO,SAAS,QAAQ,OAAO;AACjC;;;ACzKA,IAAAA,gBAAyB;AAEzB,IAAAC,iBAAsC;AACtC,IAAAD,gBAAwB;AAmJxB,IAAM,kCAA8B,sCAIlC,2BAA2B;AAE7B,IAAM,oCAAgC,sCAIpC,6BAA6B;AAE/B,IAAM,4CAAwC,sCAI5C,qCAAqC;AAEvC,IAAM,gDAA4C,sCAIhD,yCAAyC;AAE3C,IAAM,qCAAiC,sCAIrC,8BAA8B;AAKhC,IAAM,iDAA6C,sCAIjD,0CAA0C;AAK5C,IAAM,8CAA0C,sCAI9C,uCAAuC;AAEzC,IAAM,8CAA0C,sCAI9C,uCAAuC;AAMzC,IAAM,0CAAsC,sCAI1C,mCAAmC;AAErC,IAAM,0CAAsC,sCAI1C,mCAAmC;AAErC,IAAM,sCAAkC,sCAItC,+BAA+B;AAMjC,IAAME,QAAO;AAEb,SAASC,UACP,OACA,SACmB;AACnB,MAAI,SAAS;AACX,WAAO,EAAE,MAAM,QAAW,SAAS,OAAO,OAAO,OAAU;AAAA,EAC7D;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,UAAU;AAAA,IACnB,OAAO;AAAA,EACT;AACF;AA0CA,SAAS,iBACP,SACwC;AAlW1C;AAmWE,QAAM,iBAAgB,mBAAQ,WAAR,mBAAgB,WAAhB,YAA0B;AAChD,QAAM,eAAe,cAAc,SAAS;AAC5C,QAAM,UAAU,CAAC,QAAQ;AAEzB,QAAM,iBAAa;AAAA,IACjB;AAAA,IACA,WAAW,eACPD,QACA;AAAA,MACE,QAAQ,QAAQ;AAAA,MAChB,OAAO,QAAQ;AAAA,IACjB;AAAA,EACN;AAEA,QAAM,mBAAe;AAAA,IACnB;AAAA,IACA,WAAW,CAAC,eACRA,QACA;AAAA,MACE,QAAQ,QAAQ;AAAA,MAChB,QAAQ;AAAA,MACR,OAAO,QAAQ;AAAA,IACjB;AAAA,EACN;AAEA,SAAOC,UAAS,eAAe,eAAe,YAAY,OAAO;AACnE;AAUA,SAAS,2BACP,WACwC;AACxC,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAUD,QAAO,EAAE,UAA+B;AAAA,EACpD;AAEA,SAAOC,UAAS,QAAQ,OAAO;AACjC;AAWA,SAAS,YACP,gBACA,SAC0C;AAC1C,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UACID,QACA;AAAA,MACE;AAAA,MACA,OAAO,mCAAS;AAAA,IAClB;AAAA,EACN;AAEA,SAAOC,UAAS,QAAQ,OAAO;AACjC;AASA,SAAS,eACP,QACwB;AACxB,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAUD,QAAO,EAAE,OAAyB;AAAA,EAC9C;AAEA,SAAOC,UAAS,QAAQ,OAAO;AACjC;AAQA,SAAS,mBACP,QACA,SACsE;AACtE,QAAM,UAAU,CAAC;AACjB,QAAM,SAAS,mCAAS;AACxB,QAAM,eAAe,kBAAkB,MAAM;AAC7C,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UACID,QACA;AAAA,MACE;AAAA,MACA,gBAAgB;AAAA,IAClB;AAAA,EACN;AACA,SAAOC,UAAS,QAAQ,OAAO;AACjC;AAIA,SAAS,kBAAkB,QAAoD;AAC7E,QAAM,MAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;AACpD,aAAO;AAAA,IACL,MAAO,UAAU,OAAO,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,IAAI;AAAA;AAAA,IAEtD,CAAC,GAAG;AAAA,EACN;AACF;AAOA,SAAS,qBACP,gBACuC;AACvC,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAUD,QAAO,EAAE,eAAyC;AAAA,EAC9D;AAEA,SAAOC,UAAS,QAAQ,OAAO;AACjC;AAOA,SAAS,qBACP,gBACuC;AACvC,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAUD,QAAO,EAAE,eAAyC;AAAA,EAC9D;AAEA,SAAOC,UAAS,QAAQ,OAAO;AACjC;AAQA,SAAS,gCACP,WACuC;AACvC,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAUD,QAAO,EAAE,UAA+B;AAAA,EACpD;AAEA,SAAOC,UAAS,QAAQ,OAAO;AACjC;AASA,SAAS,4BACP,QACA,SAC8C;AAC9C,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAUD,QAAO,EAAE,QAA0B,OAAO,mCAAS,MAAM;AAAA,EACrE;AAEA,SAAOC,UAAS,QAAQ,OAAO;AACjC;AAEA,SAAS,gCACP,WACuC;AACvC,QAAM,UAAU,CAAC;AACjB,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAUD,QAAO,EAAE,UAA+B;AAAA,EACpD;AAEA,SAAOC,UAAS,QAAQ,OAAO;AACjC;;;ACpiBA,IAAAC,gBAAyB;AACzB,IAAAA,gBAA0B;AAW1B,IAAAC,iBAAsC;AAGtC,IAAM,sBAAkB,sCAStB,eAAe;AAEjB,IAAM,qBAAiB,sCAIrB,cAAc;AAEhB,IAAM,6BAAyB,sCAI7B,yBAAyB;AAE3B,IAAM,0BAAsB,sCAI1B,sBAAsB;AAGxB,IAAM,0BAAsB,sCAU1B,mBAAmB;AAErB,IAAM,yBAAqB,sCAIzB,kBAAkB;AAEpB,IAAM,iCAA6B,sCAIjC,6BAA6B;AAgB/B,SAAS,YAAY,SAAiC;AACpD,aAAO,wBAAS,iBAA+C,4BAAW,CAAC,CAAC;AAC9E;AAKA,SAAS,WAAW,IAAY;AAC9B,aAAO,wBAAS,gBAA8C,EAAE,GAAG,CAAC;AACtE;AAKA,SAAS,sBAAsB,WAAmB;AAChD,aAAO,wBAAS,wBAAsD;AAAA,IACpE;AAAA,EACF,CAAC;AACH;AAKA,SAAS,mBAAmB,QAAgB;AAC1C,aAAO,wBAAS,qBAAmD;AAAA,IACjE;AAAA,EACF,CAAC;AACH;AAiBA,SAAS,gBAAgB,SAAqC;AAC5D,aAAO;AAAA,IACL;AAAA,IACA,4BAAW,CAAC;AAAA,EACd;AACF;AAKA,SAAS,eAAe,IAAY;AAClC,aAAO,wBAAS,oBAAkD,EAAE,GAAG,CAAC;AAC1E;AAKA,SAAS,0BAA0B,WAAmB;AACpD,aAAO,wBAAS,4BAA0D;AAAA,IACxE;AAAA,EACF,CAAC;AACH;AAoBA,IAAM,mCAA+B,sCAInC,4BAA4B;AAE9B,IAAM,kCAA8B,sCAIlC,2BAA2B;AAa7B,SAAS,0BAA0B,KAA2B;AAC5D,aAAO;AAAA,IACL;AAAA,IACA,OAAO,IAAI,SAAS,IAAI,EAAE,IAAI,IAAI;AAAA,EACpC;AACF;AAKA,SAAS,wBAAwB,IAAwB;AACvD,aAAO;AAAA,IACL;AAAA,IACA,OAAO,SAAY,EAAE,GAAG,IAAI;AAAA,EAC9B;AACF;AAMA,IAAM,8BAA0B,sCAI9B,+CAA+C;AAEjD,IAAM,2BAAuB,sCAI3B,4CAA4C;AAE9C,IAAM,4BAAwB,sCAI5B,6CAA6C;AAE/C,IAAM,+BAA2B,sCAI/B,gDAAgD;AA2BlD,SAAS,kBACP,WACA,SACA;AACA,QAAM,kBAAc;AAAA,IAClB;AAAA,IACA,cAAc,SAAY,EAAE,kBAAkB,UAAU,IAAI;AAAA,EAC9D;AAEA,QAAM,eAAW;AAAA,IACf;AAAA,IACA,cAAc,SAAY,EAAE,kBAAkB,UAAU,IAAI;AAAA,EAC9D;AAEA,QAAM,gBAAY;AAAA,IAChB;AAAA,IACA,cAAc,SAAY,EAAE,kBAAkB,UAAU,IAAI;AAAA,EAC9D;AAEA,QAAM,mBAAe;AAAA,IACnB;AAAA,IACA,cAAc,SAAY,EAAE,kBAAkB,UAAU,IAAI;AAAA,EAC9D;AAEA,+BAAU,MAAM;AACd,QAAI,cAAc,WAAa,mCAAS,cAAa;AACnD;AAAA,IACF;AACA,UAAM,aAAa,mCAAS;AAC5B,UAAM,SAAS,mCAAS;AACxB,QAAI,CAAC,cAAc,CAAC,QAAQ;AAC1B;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,gBAAgB;AACvC,SAAK,MAAM,GAAG,UAAU,iCAAiC;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,aAAa;AAAA,MACf;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,UAAU,CAAC;AAAA,MAClC,QAAQ,WAAW;AAAA,IACrB,CAAC,EAAE,MAAM,MAAM;AAAA,IAEf,CAAC;AAED,WAAO,MAAM,WAAW,MAAM;AAAA,EAChC,GAAG,CAAC,WAAW,mCAAS,YAAY,mCAAS,QAAQ,mCAAS,WAAW,CAAC;AAE1E,SAAO,EAAE,aAAa,UAAU,WAAW,aAAa;AAC1D;AAWA,IAAM,4BAAwB,sCAI5B,gCAAgC;AAElC,IAAM,yBAAqB,sCAIzB,0BAA0B;AAE5B,IAAM,yBAAqB,sCAIzB,gCAAgC;AAElC,IAAM,0BAAsB,sCAI1B,4CAA4C;AAiD9C,SAAS,gBACP,OACA,SACuB;AAhazB;AAiaE,QAAM,iBAAa;AAAA,IACjB;AAAA,IACA,MAAM,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI;AAAA,EACnE;AAEA,QAAM,cAAU;AAAA,IACd;AAAA,IACA,MAAM,WAAW,SAAY,EAAE,QAAQ,MAAM,OAAO,IAAI;AAAA,EAC1D;AAEA,+BAAU,MAAM;AACd,QAAI,mCAAS,aAAa;AACxB;AAAA,IACF;AACA,QAAI,CAAC,MAAM,UAAU,MAAM,cAAc,QAAW;AAClD;AAAA,IACF;AACA,UAAM,aAAa,mCAAS;AAC5B,UAAM,SAAS,mCAAS;AACxB,QAAI,CAAC,cAAc,CAAC,QAAQ;AAC1B;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,gBAAgB;AACvC,SAAK,MAAM,GAAG,UAAU,+BAA+B;AAAA,MACrD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,aAAa;AAAA,MACf;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,QAAQ,MAAM;AAAA,QACd,WAAW,MAAM;AAAA,MACnB,CAAC;AAAA,MACD,QAAQ,WAAW;AAAA,IACrB,CAAC,EAAE,MAAM,MAAM;AAAA,IAEf,CAAC;AAED,WAAO,MAAM,WAAW,MAAM;AAAA,EAChC,GAAG;AAAA,IACD,MAAM;AAAA,IACN,MAAM;AAAA,IACN,mCAAS;AAAA,IACT,mCAAS;AAAA,IACT,mCAAS;AAAA,EACX,CAAC;AAKD,QAAM,iBACJ,eAAe,SACX,SACA,eAAe,OACb,QACE,gBAAW,QAAX,YAA8C;AACxD,QAAM,cACJ,YAAY,SACR,SACA,YAAY,OACV,QACE,aAAQ,QAAR,YAA2C;AAErD,QAAM,iBACJ,MAAM,cAAc,UAAa,eAAe;AAClD,QAAM,cAAc,MAAM,WAAW,UAAa,YAAY;AAE9D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,kBAAkB;AAAA,EAC7B;AACF;AAMA,SAAS,qBAAqB,SAA6B;AACzD,aAAO;AAAA,IACL;AAAA,IACA,UAAU,EAAE,QAAQ,IAAI;AAAA,EAC1B;AACF;AAcA,SAAS,gBACP,WACA,SACA;AACA,QAAM,YAAQ;AAAA,IACZ;AAAA,IACA,cAAc,SAAY,EAAE,kBAAkB,UAAU,IAAI;AAAA,EAC9D;AAEA,+BAAU,MAAM;AACd,QAAI,mCAAS,aAAa;AACxB;AAAA,IACF;AACA,QAAI,cAAc,QAAW;AAC3B;AAAA,IACF;AACA,UAAM,aAAa,mCAAS;AAC5B,UAAM,SAAS,mCAAS;AACxB,QAAI,CAAC,cAAc,CAAC,QAAQ;AAC1B;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,gBAAgB;AACvC,SAAK,MAAM,GAAG,UAAU,+BAA+B;AAAA,MACrD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,aAAa;AAAA,MACf;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,UAAU,CAAC;AAAA,MAClC,QAAQ,WAAW;AAAA,IACrB,CAAC,EAAE,MAAM,MAAM;AAAA,IAEf,CAAC;AAED,WAAO,MAAM,WAAW,MAAM;AAAA,EAChC,GAAG,CAAC,WAAW,mCAAS,YAAY,mCAAS,QAAQ,mCAAS,WAAW,CAAC;AAE1E,SAAO;AACT;AAyBA,IAAM,0BAAsB,sCAI1B,kCAAkC;AAEpC,IAAM,kCAA8B,sCAIlC,0CAA0C;AAiB5C,SAAS,wBACP,OACA,SACmC;AACnC,QAAM,UAAU,CAAC,CAAC,MAAM,UAAU,CAAC,CAAC,MAAM;AAC1C,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UACI;AAAA,MACE,QAAQ,MAAM;AAAA,MACd,QAAQ,MAAM;AAAA,MACd,OAAO,mCAAS;AAAA,IAClB,IACA;AAAA,EACN;AAEA,QAAM,cAAU;AAAA,IACd;AAAA,IACA,CAAC,WAAW,MAAM,iBACd,EAAE,gBAAgB,MAAM,gBAAgB,OAAO,mCAAS,MAAM,IAC9D;AAAA,EACN;AAEA,SAAO,UAAU,SAAS;AAC5B;;;AClmBA,IAAAC,gBAAyD;;;ACVlD,SAAS,qBAA8B;AAC5C,SACE,OAAO,WAAW,eAClB,mBAAmB,aACnB,iBAAiB;AAErB;AAEA,SAAsB,sBACpB,OAAO,gBAC6B;AAAA;AACpC,WAAO,UAAU,cAAc,SAAS,IAAI;AAAA,EAC9C;AAAA;AAEA,SAAsB,gBACpB,cACA,gBAC2B;AAAA;AAC3B,UAAM,WAAW,MAAM,aAAa,YAAY,gBAAgB;AAChE,QAAI,UAAU;AACZ,aAAO;AAAA,IACT;AAEA,WAAO,aAAa,YAAY,UAAU;AAAA,MACxC,iBAAiB;AAAA,MACjB,sBAAsB;AAAA,QACpB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAEO,SAAS,mBAAmB,KAGjC;AA9CF;AA+CE,QAAM,OAAO,IAAI,OAAO;AACxB,SAAO;AAAA,IACL,UAAU,IAAI;AAAA,IACd,MAAM;AAAA,MACJ,SAAQ,gBAAK,SAAL,mBAAW,WAAX,YAAqB;AAAA,MAC7B,OAAM,gBAAK,SAAL,mBAAW,SAAX,YAAmB;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,SAAS,sBAAsB,cAAkC;AAC/D,QAAM,UAAU,IAAI,QAAQ,IAAK,aAAa,SAAS,KAAM,CAAC;AAC9D,QAAM,UAAU,eAAe,SAAS,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC5E,QAAM,UAAU,KAAK,MAAM;AAC3B,QAAM,cAAc,IAAI,WAAW,QAAQ,MAAM;AACjD,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,EAAE,GAAG;AACvC,gBAAY,CAAC,IAAI,QAAQ,WAAW,CAAC;AAAA,EACvC;AACA,SAAO;AACT;;;AD7BA,SAAe,WAAoD;AAAA;AAQjE,QAAI;AAEF,aAAO,QAAQ,oBAAoB;AAAA,IACrC,SAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAmDO,SAAS,iBACd,SACwB;AAxG1B;AAyGE,QAAM,CAAC,kBAAkB,mBAAmB,QAC1C,wBAA2B,SAAS;AACtC,QAAM,CAAC,iBAAiB,kBAAkB,QAAI,wBAAwB,IAAI;AAC1E,QAAM,cAAU,sBAAuC,IAAI;AAC3D,QAAM,eAAW,sBAAO,KAAK;AAC7B,QAAM,kBAAc,uBAAsB,aAAQ,mBAAR,YAA0B,IAAI;AAExE,+BAAU,MAAM;AACd,QAAI,UAAU;AACd,UAAM,MAAY;AAlHtB,UAAAC;AAmHM,YAAM,OAAO,MAAM,SAAS;AAC5B,UAAI,CAAC,SAAS;AACZ;AAAA,MACF;AACA,cAAQ,UAAU;AAElB,UAAI,MAAM;AACR,YAAI;AACF,gBAAM,OAAO,MAAM,KAAK,oBAAoB;AAC5C,cAAI,CAAC,SAAS;AACZ;AAAA,UACF;AACA,8BAAoB,UAAU,6BAAM,MAAM,CAAC;AAAA,QAC7C,SAAQ;AACN,8BAAoB,SAAS;AAAA,QAC/B;AACA;AAAA,MACF;AAEA,UAAI,mBAAmB,GAAG;AACxB,iBAAS,UAAU;AACnB,YAAI,CAAC,YAAY,SAAS;AACxB,cAAI;AACF,kBAAM,MAAM,MAAM;AAAA,cAChB,GAAG,QAAQ,UAAU;AAAA,cACrB;AAAA,gBACE,SAAS;AAAA,kBACP,QAAQ;AAAA,kBACR,aAAa,QAAQ;AAAA,gBACvB;AAAA,cACF;AAAA,YACF;AACA,gBAAI,IAAI,IAAI;AACV,oBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,0BAAY,WAAUA,MAAA,KAAK,mBAAL,OAAAA,MAAuB;AAAA,YAC/C;AAAA,UACF,SAAQ;AAAA,UAER;AAAA,QACF;AACA,YAAI,CAAC,SAAS;AACZ;AAAA,QACF;AACA,cAAM,UACJ,OAAO,iBAAiB,cACpB,aAAa,aACb;AACN;AAAA,UACE,YAAY,YACR,YACA,YAAY,WACV,WACA;AAAA,QACR;AAAA,MACF,OAAO;AACL,4BAAoB,SAAS;AAAA,MAC/B;AAAA,IACF,IAAG;AACH,WAAO,MAAM;AACX,gBAAU;AAAA,IACZ;AAAA,EACF,GAAG,CAAC,QAAQ,YAAY,QAAQ,MAAM,CAAC;AAEvC,QAAM,eAAW,2BAAY,MAAY;AAlL3C,QAAAA,KAAA;AAmLI,QAAI,CAAC,QAAQ,QAAQ;AACnB,aAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAAA,IAC/C;AAGA,QAAI,SAAS,SAAS;AACpB,YAAM,WAAW,YAAY;AAC7B,UAAI,CAAC,UAAU;AACb,eAAO,EAAE,IAAI,OAAO,QAAQ,eAAe;AAAA,MAC7C;AAEA,YAAM,UAAU,MAAM,aAAa,kBAAkB;AACrD;AAAA,QACE,YAAY,YACR,YACA,YAAY,WACV,WACA;AAAA,MACR;AACA,UAAI,YAAY,WAAW;AACzB,eAAO,EAAE,IAAI,OAAO,QAAQ,oBAAoB;AAAA,MAClD;AAEA,UAAI;AACF,cAAM,UAASA,MAAA,QAAQ,sBAAR,OAAAA,MAA6B;AAC5C,cAAM,eAAe,MAAM,sBAAsB,MAAM;AACvD,cAAM,UAAU,cAAc;AAC9B,cAAM,eAAe,MAAM,gBAAgB,cAAc,QAAQ;AACjE,cAAM,UAAU,mBAAmB,YAAY;AAC/C,2BAAmB,aAAa,QAAQ;AAExC,cAAMC,OAAM,MAAM;AAAA,UAChB,GAAG,QAAQ,UAAU;AAAA,UACrB;AAAA,YACE,QAAQ;AAAA,YACR,SAAS;AAAA,cACP,gBAAgB;AAAA,cAChB,aAAa,QAAQ;AAAA,YACvB;AAAA,YACA,MAAM,KAAK,UAAU;AAAA,cACnB,QAAQ,QAAQ;AAAA,cAChB,UAAU;AAAA,cACV,qBAAqB;AAAA,cACrB,YAAY,QAAQ;AAAA,cACpB,QAAQ,UAAU;AAAA,cAClB,UAAU,KAAK,eAAe,EAAE,gBAAgB,EAAE;AAAA,YACpD,CAAC;AAAA,UACH;AAAA,QACF;AACA,YAAI,CAACA,KAAI,IAAI;AACX,gBAAM,OAAO,MAAMA,KAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,iBAAO;AAAA,YACL,IAAI;AAAA,YACJ,QAAQ,mBAAmBA,KAAI,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,UAC9D;AAAA,QACF;AACA,eAAO,EAAE,IAAI,KAAK;AAAA,MACpB,SAAS,KAAK;AACZ,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,QAAQ,mBAAmB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QAC7E;AAAA,MACF;AAAA,IACF;AAGA,UAAM,QAAO,aAAQ,YAAR,YAAoB,MAAM,SAAS;AAChD,YAAQ,UAAU;AAClB,QAAI,CAAC,MAAM;AACT,aAAO,EAAE,IAAI,OAAO,QAAQ,6BAA6B;AAAA,IAC3D;AAEA,QAAI,OAAO,MAAM,KAAK,oBAAoB;AAC1C,SAAI,6BAAM,YAAW,WAAW;AAC9B,aAAO,MAAM,KAAK,wBAAwB;AAAA,IAC5C;AACA,wBAAoB,UAAU,6BAAM,MAAM,CAAC;AAC3C,SAAI,6BAAM,YAAW,WAAW;AAC9B,aAAO,EAAE,IAAI,OAAO,QAAQ,oBAAoB;AAAA,IAClD;AAEA,UAAM,YAAY,MAAM,KAAK,wBAAwB;AACrD,UAAM,cAAc,uCAAW;AAC/B,UAAM,WAAW,eAAe,uCAAW,IAAI;AAC/C,QAAI,CAAC,eAAgB,aAAa,SAAS,aAAa,WAAY;AAClE,aAAO,EAAE,IAAI,OAAO,QAAQ,kBAAkB;AAAA,IAChD;AAEA,uBAAmB,WAAW;AAE9B,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,QAAQ,UAAU;AAAA,MACrB;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,QAAQ;AAAA,QACvB;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,QAAQ,QAAQ;AAAA,UAChB;AAAA,UACA;AAAA,UACA,YAAY,QAAQ;AAAA,UACpB,QACE,OAAO,cAAc,cAAc,UAAU,WAAW;AAAA,UAC1D,UAAU,KAAK,eAAe,EAAE,gBAAgB,EAAE;AAAA,WAC9C,aAAa,SAAS,QAAQ,kBAC9B,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC,EACN;AAAA,MACH;AAAA,IACF;AACA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,QAAQ,mBAAmB,IAAI,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MAC9D;AAAA,IACF;AACA,WAAO,EAAE,IAAI,KAAK;AAAA,EACpB,IAAG;AAAA,IACD,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AAED,QAAM,iBAAa,2BAAY,MAAY;AACzC,QAAI,CAAC,iBAAiB;AACpB;AAAA,IACF;AACA,UAAM,MAAM,GAAG,QAAQ,UAAU,yCAAyC;AAAA,MACxE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,aAAa,QAAQ;AAAA,MACvB;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,aAAa,gBAAgB,CAAC;AAAA,IACvD,CAAC,EAAE,MAAM,MAAM;AAAA,IAEf,CAAC;AACD,uBAAmB,IAAI;AAAA,EACzB,IAAG,CAAC,QAAQ,YAAY,QAAQ,QAAQ,eAAe,CAAC;AAExD,QAAM,0BAAsB;AAAA,IAC1B,CAAC,aAAiD;AAChD,UAAI,SAAS,SAAS;AACpB,YACE,OAAO,cAAc,eACrB,EAAE,mBAAmB,YACrB;AACA,iBAAO,MAAM;AAAA,UAAC;AAAA,QAChB;AACA,cAAM,UAAU,CAAC,UAAwB;AA7UjD,cAAAD;AA8UU,gBAAIA,MAAA,MAAM,SAAN,gBAAAA,IAAY,UAAS,uBAAuB;AAC9C,qBAAS,MAAM,KAAK,OAAO;AAAA,UAC7B;AAAA,QACF;AACA,kBAAU,cAAc,iBAAiB,WAAW,OAAO;AAC3D,eAAO,MACL,UAAU,cAAc,oBAAoB,WAAW,OAAO;AAAA,MAClE;AACA,YAAM,OAAO,QAAQ;AACrB,UAAI,EAAC,6BAAM,kCAAiC;AAC1C,eAAO,MAAM;AAAA,QAAC;AAAA,MAChB;AACA,YAAM,MAAM,KAAK,gCAAgC,QAAQ;AACzD,aAAO,MAAG;AA3VhB,YAAAA;AA2VmB,gBAAAA,MAAA,IAAI,WAAJ,gBAAAA,IAAA;AAAA;AAAA,IACf;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,0BAAsB;AAAA,IAC1B,CAAC,aAAiD;AAChD,UAAI,SAAS,SAAS;AACpB,YACE,OAAO,cAAc,eACrB,EAAE,mBAAmB,YACrB;AACA,iBAAO,MAAM;AAAA,UAAC;AAAA,QAChB;AACA,cAAM,UAAU,CAAC,UAAwB;AAzWjD,cAAAA;AA0WU,gBAAIA,MAAA,MAAM,SAAN,gBAAAA,IAAY,UAAS,qBAAqB;AAC5C,qBAAS,MAAM,KAAK,OAAO;AAAA,UAC7B;AAAA,QACF;AACA,kBAAU,cAAc,iBAAiB,WAAW,OAAO;AAC3D,eAAO,MACL,UAAU,cAAc,oBAAoB,WAAW,OAAO;AAAA,MAClE;AACA,YAAM,OAAO,QAAQ;AACrB,UAAI,EAAC,6BAAM,0CAAyC;AAClD,eAAO,MAAM;AAAA,QAAC;AAAA,MAChB;AACA,YAAM,MAAM,KAAK,wCAAwC,QAAQ;AACjE,aAAO,MAAG;AAvXhB,YAAAA;AAuXmB,gBAAAA,MAAA,IAAI,WAAJ,gBAAAA,IAAA;AAAA;AAAA,IACf;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,oBAAgB,2BAAY,MAA6B;AA5XjE,QAAAA;AA6XI,UAAM,OAAO,QAAQ;AACrB,QAAI,EAAC,6BAAM,qBAAoB;AAC7B,aAAO;AAAA,IACT;AACA,YAAQA,MAAA,MAAM,KAAK,mBAAmB,MAA9B,OAAAA,MAAoC;AAAA,EAC9C,IAAG,CAAC,CAAC;AAEL,QAAM,oBAAgB,2BAAY,CAAO,UAAiC;AACxE,UAAM,OAAO,QAAQ;AACrB,QAAI,EAAC,6BAAM,qBAAoB;AAC7B;AAAA,IACF;AACA,UAAM,KAAK,mBAAmB,KAAK;AAAA,EACrC,IAAG,CAAC,CAAC;AAGL,QAAM,eAAe,QAAQ,iBAAiB;AAC9C,+BAAU,MAAM;AACd,QAAI,CAAC,cAAc;AACjB;AAAA,IACF;AACA,QAAI,qBAAqB,WAAW;AAClC;AAAA,IACF;AACA,QAAI,iBAAiB;AACnB;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,QAAQ;AACnB;AAAA,IACF;AACA,SAAK,SAAS;AAAA,EAChB,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,UAAU,QAA8C;AAC/D,MAAI,WAAW,WAAW;AACxB,WAAO;AAAA,EACT;AACA,MAAI,WAAW,UAAU;AACvB,WAAO;AAAA,EACT;AACA,MAAI,WAAW,gBAAgB;AAC7B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,eACP,WACuC;AAIvC,MAAI,cAAc,SAAS,cAAc,QAAQ;AAC/C,WAAO;AAAA,EACT;AACA,MAAI,cAAc,aAAa,cAAc,OAAO;AAClD,WAAO;AAAA,EACT;AACA,MAAI,cAAc,OAAO;AACvB,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AE3aA,IAAAE,gBAAyB;AAEzB,IAAAC,iBAAsC;AA4DtC,IAAM,+BAA2B,sCAQ/B,4BAA4B;AAM9B,IAAMC,QAAO;AAmBb,SAAS,wBACP,OACmC;AACnC,QAAM,cAAc,CAAC,EAAC,+BAAO;AAC7B,QAAM,kBAAkB,CAAC,GAAE,+BAAO,iBAAgB,MAAM,aAAa,SAAS;AAC9E,QAAM,cAAc,eAAe;AAEnC,QAAM,OAAO,cACT,kDACM,+BAAO,YAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC,KAClD,+BAAO,iBAAgB,MAAM,aAAa,SAAS,IACnD,EAAE,cAAc,MAAM,aAAa,IACnC,CAAC,KACD,+BAAO,iBAAgB,EAAE,eAAe,MAAM,cAAc,IAAI,CAAC,KAEvEA;AAEJ,QAAM,aAAS;AAAA,IACb;AAAA,IACA;AAAA,EACF;AAEA,MAAI,CAAC,aAAa;AAChB,WAAO,EAAE,MAAM,QAAW,SAAS,OAAO,OAAO,OAAU;AAAA,EAC7D;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,WAAW;AAAA,IACpB,OAAO;AAAA,EACT;AACF;;;ACtHA,IAAAC,gBAAyB;AAEzB,IAAAC,iBAAsC;AAsEtC,IAAM,wBAAoB,sCASxB,iBAAiB;AAMnB,IAAMC,QAAO;AAiBN,SAAS,iBACd,SACuC;AApJzC;AAqJE,QAAM,iBAAgB,aAAQ,UAAR,YAAiB,IAAI,KAAK;AAChD,QAAM,UAAU,aAAa,WAAW;AAExC,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UACIA,QACA;AAAA,MACE,OAAO;AAAA,OACH,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC,IACrD,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC,IACzC,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,EAEtE;AAEA,MAAI,SAAS;AACX,WAAO,EAAE,MAAM,QAAW,SAAS,OAAO,OAAO,OAAU;AAAA,EAC7D;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,WAAW;AAAA,IACpB,OAAO;AAAA,EACT;AACF;;;ACjKA,IAAAC,gBAAyB;AAEzB,IAAAC,iBAAsC;AACtC,IAAAD,iBAAwB;AAIxB,IAAM,0BAAsB,sCAI1B,mBAAmB;AAErB,IAAM,6BAAyB,sCAI7B,sBAAsB;AAExB,IAAME,QAAO;AASN,SAAS,iBACd,KACyC;AACzC,QAAM,gBAAY,wBAAQ,MAAM;AAC9B,UAAM,MAAM,oBAAO,CAAC;AACpB,WAAO,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,EAAE,KAAK;AAAA,EAChC,GAAG,CAAC,GAAG,CAAC;AACR,QAAM,UAAU,UAAU,WAAW;AAErC,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAUA,QAAO,EAAE,KAAK,UAAU;AAAA,EACpC;AAEA,QAAM,aAAS,wBAAQ,MAAM;AAC3B,QAAI,WAAW,OAAW,QAAO;AACjC,WAAO,OAAO;AAAA,MACZ,OAAO,IAAI,CAAC,MAAM;AAzDxB;AA0DQ,cAAM,MACH,aAAoC,OAApC,YACA,EAAuB,QADvB,YAED;AACF,eAAO,CAAC,OAAO,EAAE,GAAG,CAAC;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,MAAI,SAAS;AACX,WAAO,EAAE,MAAM,CAAC,GAAG,SAAS,OAAO,OAAO,OAAU;AAAA,EACtD;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,WAAW;AAAA,IACpB,OAAO;AAAA,EACT;AACF;AAMO,SAAS,oBACd,QACyC;AACzC,QAAM,mBAAe,wBAAQ,MAAM;AACjC,UAAM,MAAM,0BAAU,CAAC;AACvB,UAAM,SAAS,IACZ,IAAI,CAAC,MAAM,EAAE,QAAQ,QAAQ,EAAE,CAAC,EAChC,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC7B,WAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,EAAE,KAAK;AAAA,EACnC,GAAG,CAAC,MAAM,CAAC;AACX,QAAM,UAAU,aAAa,WAAW;AAExC,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAUA,QAAO,EAAE,aAAa,aAAa;AAAA,EAC/C;AAEA,QAAM,aAAS,wBAAQ,MAAM;AAC3B,QAAI,WAAW,OAAW,QAAO;AACjC,WAAO,OAAO,YAAY,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;AAAA,EACzE,GAAG,CAAC,MAAM,CAAC;AAEX,MAAI,SAAS;AACX,WAAO,EAAE,MAAM,CAAC,GAAG,SAAS,OAAO,OAAO,OAAU;AAAA,EACtD;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,WAAW;AAAA,IACpB,OAAO;AAAA,EACT;AACF;;;ACzFA,IAAAC,iBAAkD;AAElD,IAAAA,iBAAuC;AAQhC,IAAM,cAAsC;AAAA,EACjD,OAAO;AAAA,EACP,SAAS;AAAA,EACT,KAAK;AAAA,EACL,SAAS;AAAA,EACT,KAAK;AAAA,EACL,YAAY;AACd;AAMO,SAAS,iBACd,aACA,UACQ;AAhDV;AAiDE,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AACA,QAAM,MAAM,oCAAe;AAC3B,UAAO,iBAAY,GAAG,MAAf,YAAoB,YAAY;AACzC;AAUA,SAAS,cAAc;AAAA,EACrB,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,MAAM,iBAAiB,aAAa,SAAS;AAEnD,QAAM,aAAS,wBAAQ,MAAM,IAAI,iCAAkB,GAAG,GAAG,CAAC,GAAG,CAAC;AAE9D,aAAO,8BAAc,+BAAgB,EAAE,OAAO,GAAG,QAAQ;AAC3D;;;AC/DA,IAAAC,iBAAyB;AAEzB,IAAAC,iBAAsC;AAItC,IAAM,+CAA2C,sCAI/C,wCAAwC;AAE1C,IAAMC,QAAO;AAiBN,SAAS,6BACd,iBAC0C;AAC1C,QAAM,MAAM,4CAAmB,CAAC;AAChC,QAAM,UAAU,IAAI,WAAW;AAE/B,QAAM,aAAS;AAAA,IACb;AAAA,IACA,UAAUA,QAAO,EAAE,iBAAiB,IAAI;AAAA,EAC1C;AAEA,QAAM,SACJ,WAAW,SACP,SACA,OAAO,YAAY,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAEjE,MAAI,SAAS;AACX,WAAO,EAAE,MAAM,CAAC,GAAG,SAAS,OAAO,OAAO,OAAU;AAAA,EACtD;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,WAAW;AAAA,IACpB,OAAO;AAAA,EACT;AACF;;;ACrCA,IAAAC,iBAAkE;;;ACIlE,SAAS,iBAAyB;AAChC,QAAM,MAAM,KAAK,IAAI;AAGrB,QAAM,YAAY,IAAI,WAAW,CAAC;AAClC,MAAI,KAAK;AACT,WAAS,IAAI,GAAG,KAAK,GAAG,KAAK;AAC3B,cAAU,CAAC,IAAI,KAAK;AACpB,SAAK,KAAK,MAAM,KAAK,GAAG;AAAA,EAC1B;AAGA,QAAM,cAAc,IAAI,WAAW,EAAE;AACrC,MACE,OAAO,WAAW,WAAW,eAC7B,WAAW,OAAO,iBAClB;AACA,eAAW,OAAO,gBAAgB,WAAW;AAAA,EAC/C,OAAO;AACL,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,kBAAY,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAAA,IACjD;AAAA,EACF;AAGA,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,QAAM,IAAI,WAAW,CAAC;AACtB,QAAM,IAAI,aAAa,CAAC;AAGxB,QAAM,CAAC,IAAK,MAAM,CAAC,IAAI,KAAQ;AAG/B,QAAM,CAAC,IAAK,MAAM,CAAC,IAAI,KAAQ;AAG/B,QAAM,MAAM,MAAM,KAAK,KAAK,EACzB,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAC1C,KAAK,EAAE;AAEV,SAAO;AAAA,IACL,IAAI,MAAM,GAAG,CAAC;AAAA,IACd,IAAI,MAAM,GAAG,EAAE;AAAA,IACf,IAAI,MAAM,IAAI,EAAE;AAAA,IAChB,IAAI,MAAM,IAAI,EAAE;AAAA,IAChB,IAAI,MAAM,IAAI,EAAE;AAAA,EAClB,EAAE,KAAK,GAAG;AACZ;AAcA,IAAM,WAAmC;AAAA,EACvC,OAAO;AAAA,EACP,SAAS;AAAA,EACT,KAAK;AAAA,EACL,SAAS;AAAA,EACT,KAAK;AAAA,EACL,YAAY;AACd;AAQA,IAAM,cAAc;AACpB,IAAM,sBAAsB;AAqB5B,IAAM,UAAN,MAAc;AAAA,EASZ,YAAY,QAAuB;AANnC,SAAQ,QAAyB,CAAC;AAClC,SAAQ,aAAoD;AAE5D,SAAQ,aAAa;AACrB,SAAQ,aAAa;AAxIvB;AA2II,SAAK,SAAS;AACd,SAAK,UACH,kBAAO,eAAP,YAAqB,SAAS,OAAO,WAAW,MAAhD,YAAqD,SAAS;AAEhE,SAAK,mBAAmB;AACxB,SAAK,sBAAsB;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAA2B;AAClC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MACE,WACA,SACA,SACM;AAlKV;AAmKI,QAAI,KAAK,YAAY;AACnB;AAAA,IACF;AAEA,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,UAAM,WAA0B;AAAA,MAC9B,UAAU,eAAe;AAAA,MACzB,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,cAAa,wCAAS,eAAT,YAAuB;AAAA,MACpC,aAAa;AAAA,MACb,QAAQ,KAAK,OAAO;AAAA,MACpB,gBAAgB,KAAK,OAAO;AAAA,MAC5B,YAAW,wCAAS,aAAT,YAAqB,KAAK,OAAO;AAAA,MAC5C,QACE,wCAAS,UAAT,YACC,KAAK,eACF;AAAA,QACE,UAAU,KAAK,aAAa;AAAA,QAC5B,YAAY,KAAK,aAAa;AAAA,MAChC,IACA;AAAA,MACN,SAAS,mCAAS;AAAA,MAClB,YAAY,mCAAS;AAAA,MACrB;AAAA,IACF;AAEA,SAAK,MAAM,KAAK,QAAQ;AAExB,QAAI,KAAK,MAAM,UAAU,KAAK,OAAO,WAAW;AAC9C,WAAK,KAAK,MAAM;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,QAAuB;AAAA;AAC3B,UAAI,KAAK,MAAM,WAAW,KAAK,KAAK,YAAY;AAC9C;AAAA,MACF;AAEA,WAAK,aAAa;AAClB,YAAM,QAAQ,KAAK,MAAM,OAAO,GAAG,KAAK,OAAO,SAAS;AAExD,UAAI;AACF,cAAM,KAAK,UAAU,KAAK;AAAA,MAC5B,SAAQ;AAEN,aAAK,MAAM,QAAQ,GAAG,KAAK;AAAA,MAC7B,UAAE;AACA,aAAK,aAAa;AAAA,MACpB;AAGA,UAAI,KAAK,MAAM,UAAU,KAAK,OAAO,WAAW;AAC9C,cAAM,KAAK,MAAM;AAAA,MACnB;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,WAA0B;AAAA;AAC9B,WAAK,aAAa;AAClB,WAAK,kBAAkB;AACvB,YAAM,KAAK,MAAM;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKc,UAAU,OAAuC;AAAA;AAC7D,UAAI;AAEJ,eAAS,UAAU,GAAG,WAAW,aAAa,WAAW;AACvD,YAAI;AACF,gBAAM,WAAW,MAAM,MAAM,GAAG,KAAK,MAAM,sBAAsB;AAAA,YAC/D,QAAQ;AAAA,YACR,SAAS;AAAA,cACP,gBAAgB;AAAA,cAChB,aAAa,KAAK,OAAO;AAAA,YAC3B;AAAA,YACA,MAAM,KAAK,UAAU,EAAE,QAAQ,MAAM,CAAC;AAAA,UACxC,CAAC;AAED,cAAI,SAAS,IAAI;AACf;AAAA,UACF;AAGA,cACE,SAAS,UAAU,OACnB,SAAS,SAAS,OAClB,SAAS,WAAW,KACpB;AACA;AAAA,UACF;AAEA,sBAAY,IAAI;AAAA,YACd,QAAQ,SAAS,MAAM,KAAK,SAAS,UAAU;AAAA,UACjD;AAAA,QACF,SAAS,OAAO;AACd,sBAAY;AAAA,QACd;AAGA,YAAI,UAAU,aAAa;AACzB,gBAAM,QAAQ,sBAAsB,SAAK;AACzC,gBAAM,SAAS,KAAK,OAAO,IAAI,QAAQ;AACvC,gBAAM,MAAM,QAAQ,MAAM;AAAA,QAC5B;AAAA,MACF;AAEA,YAAM;AAAA,IACR;AAAA;AAAA,EAEQ,qBAA2B;AACjC,QAAI,KAAK,OAAO,kBAAkB,GAAG;AACnC,WAAK,aAAa,YAAY,MAAM;AAClC,aAAK,KAAK,MAAM;AAAA,MAClB,GAAG,KAAK,OAAO,eAAe;AAG9B,UAAI,OAAO,KAAK,eAAe,YAAY,WAAW,KAAK,YAAY;AACrE,aAAK,WAAW,MAAM;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,oBAA0B;AAChC,QAAI,KAAK,eAAe,MAAM;AAC5B,oBAAc,KAAK,UAAU;AAC7B,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,wBAA8B;AAEpC,QAAI,OAAO,WAAW,YAAY,eAAe,WAAW,QAAQ,IAAI;AACtE,YAAM,kBAAkB,MAAM;AAC5B,aAAK,KAAK,SAAS;AAAA,MACrB;AAEA,iBAAW,QAAQ,GAAG,cAAc,eAAe;AACnD,iBAAW,QAAQ,GAAG,WAAW,eAAe;AAAA,IAClD;AAAA,EACF;AACF;AAMA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;;;ADjRA,IAAM,2BAAuB;AAAA,EAC3B;AACF;AAoBA,SAAS,sBAAsB;AAAA,EAC7B,cAAc;AAAA,EACd,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,WAAW;AAAA,EACX,SAAS;AAAA,EACT;AACF,GAA+B;AAC7B,QAAM,YAAQ,wBAAmC,MAAM;AACrD,UAAM,UAAU,IAAI,QAAQ;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX,iBAAiB;AAAA,IACnB,CAAC;AAED,WAAO;AAAA,MACL,OAAO,CAAC,WAAW,SAAS,YAAY;AACtC,gBAAQ,MAAM,WAAW,SAAS,OAAO;AAAA,MAC3C;AAAA,MACA,UAAU,CAAC,SAAS,cAAc;AAChC,gBAAQ,SAAS,EAAE,SAAS,UAAU,CAAC;AAAA,MACzC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,aAAa,QAAQ,eAAe,QAAQ,CAAC;AAEzD,aAAO,8BAAc,qBAAqB,UAAU,EAAE,MAAM,GAAG,QAAQ;AACzE;AAUA,SAAS,WAAsC;AAC7C,QAAM,UAAM,2BAAW,oBAAoB;AAC3C,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACxE;AACA,SAAO;AACT;;;AEtGA,IAAAC,iBAAyB;AAEzB,IAAAC,iBAAsC;AAQtC,IAAM,iCAA6B,sCAIjC,0BAA0B;AAM5B,IAAMC,QAAO;AAUN,SAAS,gBACd,QACqC;AACrC,QAAM,OAAO,CAAC;AAEd,QAAM,aAAS;AAAA,IACb;AAAA,IACA,OAAOA,QAAO,EAAE,OAAyB;AAAA,EAC3C;AAEA,MAAI,MAAM;AACR,WAAO,EAAE,MAAM,MAAM,SAAS,OAAO,OAAO,OAAU;AAAA,EACxD;AAEA,SAAO;AAAA,IACL,MAAM,0BAAU;AAAA,IAChB,SAAS,WAAW;AAAA,IACpB,OAAO;AAAA,EACT;AACF;;;ACnCA,IAAAC,iBAA8C;AA4BvC,SAAS,gBAAgB,QAA4C;AAC1E,QAAM,CAAC,KAAK,MAAM,QAAI,yBAAwB,IAAI;AAClD,QAAM,CAAC,WAAW,YAAY,QAAI,yBAAS,KAAK;AAChD,QAAM,CAAC,OAAO,QAAQ,QAAI,yBAAwB,IAAI;AAGtD,QAAM,kBAAc,uBAAO,KAAK;AAEhC,QAAM,eAAW;AAAA,IACf,CAAO,kBAAkD;AACvD,UAAI,YAAY,QAAS,QAAO;AAChC,kBAAY,UAAU;AACtB,mBAAa,IAAI;AACjB,eAAS,IAAI;AAEb,UAAI;AACF,cAAM,SAAS,MAAM,OAAO,SAAS,gBAAgB,aAAa;AAClE,eAAO,OAAO,GAAG;AACjB,eAAO,OAAO;AAAA,MAChB,SAAS,KAAK;AACZ,cAAM,MAAM,eAAe,QAAQ,IAAI,UAAU;AACjD,iBAAS,GAAG;AACZ,eAAO;AAAA,MACT,UAAE;AACA,qBAAa,KAAK;AAClB,oBAAY,UAAU;AAAA,MACxB;AAAA,IACF;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,SAAO,EAAE,UAAU,KAAK,WAAW,MAAM;AAC3C;","names":["import_react","import_server","SKIP","toResult","import_react","import_server","import_react","_a","res","import_react","import_server","SKIP","import_react","import_server","SKIP","import_react","import_server","SKIP","import_react","import_react","import_server","SKIP","import_react","import_react","import_server","SKIP","import_react"]}
|