@hipnation-truth/sdk 0.25.3 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +24 -25
- package/dist/index.d.ts +24 -25
- package/dist/index.js +94 -104
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +94 -104
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.ts +24 -25
- package/dist/react.js +94 -104
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.ts","../src/react/calls.ts","../src/react/offline/use-persistent-query.ts","../src/react/conversation-by-id.ts","../src/react/conversations.ts","../src/react/hooks.ts","../src/react/provider.ts","../src/auth-convex-client.ts","../src/resources/appointments.ts","../src/resources/attachments.ts","../src/resources/conversations.ts","../src/resources/dialpad.ts","../src/resources/ehr.ts","../src/resources/notes.ts","../src/resources/notifications.ts","../src/resources/patient-details.ts","../src/resources/patients.ts","../src/resources/physicians.ts","../src/resources/reminders.ts","../src/resources/tasks.ts","../src/resources/translation.ts","../src/resources/user-settings.ts","../src/tracking/tracker.ts","../src/web-push.ts","../src/client.ts","../src/react/offline/envelope.ts","../src/react/offline/persister.ts","../src/react/offline/store.ts","../src/react/notifications.ts","../src/react/patient-family.ts","../src/react/patient-search.ts","../src/react/patients-bulk.ts","../src/react/reminders.ts","../src/react/tasks.ts","../src/react/tracking.ts","../src/react/user-settings.ts","../src/react/users.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// Conversation-by-id lookup\nexport { useConversationById } from \"./react/conversation-by-id\";\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 UseNotificationsActions,\n UseNotificationsOptions,\n UseNotificationsResult,\n} from \"./react/notifications\";\nexport {\n useNotifications,\n useNotificationsActions,\n} from \"./react/notifications\";\n// Offline reads (L2) — synchronous KV mirror seam. The RN app injects an\n// encrypted store; web omits it and gets a transparent NoopStore.\n// `readPersistedSavedAt` backs the offline/staleness indicator.\nexport { readPersistedSavedAt } from \"./react/offline/persister\";\nexport type { OfflineStore } from \"./react/offline/store\";\nexport { NoopStore } from \"./react/offline/store\";\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 {\n API_BASE_URLS,\n CONVEX_URLS,\n getTruthClient,\n resolveApiBaseUrl,\n resolveConvexUrl,\n TruthProvider,\n useOfflineEnabled,\n useOfflineStore,\n useTruthClient,\n useTruthSdkContext,\n} from \"./react/provider\";\n// Reminders\nexport { useRemindersForConversations } from \"./react/reminders\";\n// Conversation-task seen tracking\nexport { useConversationTaskMarkSeen } from \"./react/tasks\";\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// User sync (Clerk → Truth)\nexport type {\n UseUserSyncInput,\n UseUserSyncResult,\n} from \"./react/users\";\nexport { useUserSync } from \"./react/users\";\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\n// Active calls are inherently live — never mirrored (see useActiveCalls).\nimport { useQuery as useLiveQuery } from \"convex/react\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist: call history shown inside a conversation renders\n// from the mirror offline. Aliased so the call sites stay unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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 = useLiveQuery(listActiveRef, options ?? {});\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,\n skipped\n ? SKIP\n : {\n conversationId: conversationId as string,\n limit: options?.limit,\n },\n );\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,\n skipped ? SKIP : { callId: callId as string },\n );\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,\n skipped ? SKIP : { callId: callId as string, limit: options?.limit },\n );\n return toResult(result, skipped);\n}\n","/**\n * `usePersistentQuery` — the single seam that gives a Convex read hook a\n * durable, offline-readable mirror, while keeping the exact return\n * contract of `convex/react`'s `useQuery` (`data | undefined`).\n *\n * **Path A (TanStack, active).** The subscription is routed through\n * `@convex-dev/react-query`'s `convexQuery` + TanStack `useQuery`. The\n * Convex websocket still drives live updates (live always wins); the\n * difference is that the result now lives in the TanStack query cache,\n * which `<TruthProvider>` persists to the injected `OfflineStore` via\n * `PersistQueryClientProvider`. On cold start the persisted value\n * rehydrates and renders before the socket reconnects.\n *\n * **SKIP semantics are preserved.** `convexQuery(ref, \"skip\")` returns\n * `enabled: false`, so per the `@convex-dev/react-query` docs the query\n * is disabled — it shouldn't subscribe or issue a network request —\n * matching `useQuery(ref, \"skip\")` today. The call sites keep computing\n * `skipped` and passing it to `toResult`, so the\n * `{ data, loading, error }` contract is unchanged.\n *\n * **No mutation paths are touched.** This wraps reads only.\n *\n * @remarks The hand-rolled Path B implementation (raw `convex/react`\n * `useQuery` + `makeCacheKey`/`readEnvelope`/`writeEnvelope` against the\n * store) is a localized swap behind this same signature — see\n * `./cache-key` and `./envelope`.\n */\n\n\"use client\";\n\nimport { convexQuery } from \"@convex-dev/react-query\";\nimport { useQuery as useTanstackQuery } from \"@tanstack/react-query\";\nimport type { FunctionReference } from \"convex/server\";\n\n/** The Convex \"skip\" sentinel — matches the read hooks' `SKIP`. */\nexport type QueryArgs = Record<string, unknown> | \"skip\";\n\n/**\n * Drop-in replacement for `convex/react`'s `useQuery` that additionally\n * lets the result be mirrored offline. Returns the query data, or\n * `undefined` while loading / when skipped — exactly like the original.\n */\nexport function usePersistentQuery<Query extends FunctionReference<\"query\">>(\n ref: Query,\n args: Query[\"_args\"] | \"skip\",\n): Query[\"_returnType\"] | undefined {\n // `convexQuery` accepts the \"skip\" sentinel directly and returns\n // `enabled: false` for it, so we forward args verbatim — no branching\n // that could violate the rules of hooks. The inner casts keep\n // `convexQuery`'s arg type happy, while the outer generic mirrors\n // `convex/react`'s `useQuery` inference (args + return type) so an\n // aliased call site is a true drop-in with no extra annotations.\n const { data } = useTanstackQuery(\n convexQuery(\n ref as FunctionReference<\"query\">,\n args as Record<string, unknown> | \"skip\",\n ),\n );\n return data as Query[\"_returnType\"] | undefined;\n}\n","/**\n * React hook for a single conversation lookup by Convex id — Truth SDK.\n *\n * Replaces CommHub's `useGet_Conversation_EventQuery` (backed by the\n * Hasura `Get_Conversation_Event` GraphQL query). Queries the Convex\n * `conversations:getById` function which returns the raw conversation\n * row, or `null` when the id doesn't exist.\n *\n * The returned `ConversationRow` carries the same phone-pair fields as\n * `useConversationByPhonePair` — `patientPhone`, `providerPhone`,\n * `phonePair`, `patientId` — but is keyed on the Convex document `_id`\n * rather than the pair. Use this when you have a Convex id from a push\n * notification or a deep-link route param.\n *\n * Must be used within a `<TruthProvider />`.\n *\n * @example\n * ```tsx\n * import { useConversationById } from '@hipnation-truth/sdk/react/conversation-by-id';\n *\n * function ConversationScreen({ id }: { id: string }) {\n * const { data: conversation, loading } = useConversationById(id);\n * if (loading) return <Spinner />;\n * if (!conversation) return <NotFound />;\n * return <View>{conversation.patientPhone}</View>;\n * }\n * ```\n */\n\n\"use client\";\n\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { ConversationRow, UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist (Phase 2.1): a deep-linked conversation should\n// open from the mirror. Aliased so the call site below is unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\n\n// ---------------------------------------------------------------------------\n// Convex function reference\n// ---------------------------------------------------------------------------\n\nconst conversationsGetByIdRef = makeFunctionReference<\n \"query\",\n { id: string },\n ConversationRow | null\n>(\"conversations:getById\");\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to a single conversation by its Convex document id. Returns\n * `null` when no conversation exists for that id.\n *\n * `data` is `undefined` while loading or when `id` is falsy (skip). Pass\n * `null` or `undefined` to skip the query — useful when the route param\n * hasn't resolved yet.\n */\nfunction useConversationById(\n id: string | null | undefined,\n): UseQueryResult<ConversationRow | null> {\n const skipped = !id;\n const result = useQuery(\n conversationsGetByIdRef as FunctionReference<\"query\">,\n skipped ? \"skip\" : { id: id as string },\n ) as ConversationRow | null | undefined;\n\n if (skipped) {\n return { data: undefined, loading: false, error: undefined };\n }\n return {\n data: result,\n loading: result === undefined,\n error: undefined,\n };\n}\n\nexport { useConversationById };\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 type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport { useMemo } from \"react\";\n// Offline-reads allowlist: all conversation read hooks (list, search, by-pair,\n// messages, notes, tasks, unread counts/aggregate) route through the persisted\n// mirror so previously-loaded threads render offline. The wrapper preserves the\n// `data | undefined` contract, so each call site uses `usePersistentQuery` in\n// place of `convex/react`'s `useQuery`.\nimport { usePersistentQuery } from \"./offline/use-persistent-query\";\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 /**\n * Denormalized preview of the most recent message, for the inbox list +\n * right-panel snippet (so the UI shows the message instead of a literal\n * \"Conversation\"). Populated by the write path + a one-off backfill.\n */\n lastMessageText: string | null;\n /** `\"sms\" | \"call\" | \"voicemail\"` — drives the preview's icon/label. */\n lastMessageKind: string | null;\n /** `\"inbound\" | \"outbound\"` of the previewed message. */\n lastDirection: string | null;\n /**\n * Call lifecycle state when the previewed event is a call/voicemail:\n * `\"missed\" | \"connected\" | \"ringing\" | \"hangup\" | \"voicemail\" |\n * \"voicemail_uploaded\"`. Lets the inbox show \"Missed call\" / \"Call in\n * progress\" / \"Voicemail\" instead of a bare \"Call\". Null for SMS.\n */\n lastCallState: string | null;\n /** Call duration in seconds (calls only). */\n lastCallDurationSec: number | null;\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 /**\n * Task title — only standalone (New-Task-modal) tasks have one; conversation\n * tasks have none (the `description` is the content). When set, the My Tasks\n * card shows it as the headline with the real `description` below it.\n */\n title: string | null;\n /** Normalized `(patient_phone | provider_phone)` for the task's conversation. */\n phonePair: string | null;\n /**\n * Resolved patient display name. `null` when the conversation has no\n * linked patient — callers fall back to formatting `phonePair`.\n */\n patientName: string | null;\n /**\n * Set of user ids who have seen this task (opened the detail panel).\n * Replaces the Hasura `event_activities.seen` boolean — use\n * `seenBy.includes(userId)` to derive the per-user boolean.\n */\n seenBy: string[];\n /**\n * Which backend table the task lives in:\n * - `\"conversation\"` — `conversationTasks`, tied to a conversation.\n * - `\"standalone\"` — the general `tasks` table (New Task modal with\n * a patient but no conversation). For standalone rows\n * `conversationId` is `\"\"` and `phonePair` is `null`; status /\n * priority mutations must route to the `tasks` resource.\n */\n source: \"conversation\" | \"standalone\";\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 /** Voicemail/call transcript — Dialpad delivers `{ text }` (B5). */\n transcript: unknown | null;\n /** AI-generated Dialpad recap summary (calls only; null for SMS). */\n recapSummary: string | null;\n text: string | null;\n mms: boolean;\n mmsUrl: string | null;\n messageStatus: string | null;\n /**\n * True while an outbound SMS is the optimistic local row inserted by the\n * Truth `sendMessage` path and not yet reconciled by the Dialpad webhook\n * (Truth #100). Cleared once the real event lands. Always `false` for calls\n * and for reconciled rows. Clients should gate a \"sending\" affordance on\n * THIS flag — not on `messageStatus === \"optimistic\"`, a value that is\n * minted nowhere in the pipeline.\n */\n optimistic: boolean;\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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?.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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 { useEffect } from \"react\";\n// Offline-reads allowlist: patient / appointment / physician / pharmacy /\n// photo / message / medical-record reads render from the mirror offline.\n// Aliased so the call sites stay unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\nimport { useTruthSdkContext } from \"./provider\";\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 *\n * OFFLINE-READS POLICY: these medical-record queries ARE mirrored offline\n * via `usePersistentQuery`, so medications, problems, allergies and\n * appointments render from the at-rest mirror when the device is offline.\n * Full medical records are the most sensitive PHI we hold, so they are\n * only ever written to the encrypted-at-rest store (256-bit MMKV key held\n * in the device keychain), never to any plaintext storage. Keep them on\n * the encrypted mirror only — do not relax the store's encryption.\n */\nfunction usePatientMedical(\n elationId: number | undefined,\n options?: UsePatientMedicalOptions,\n) {\n const sdkContext = useTruthSdkContext();\n const apiBaseUrl = options?.apiBaseUrl ?? sdkContext?.apiBaseUrl;\n const apiKey = options?.apiKey ?? sdkContext?.apiKey;\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 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, apiBaseUrl, 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 sdkContext = useTruthSdkContext();\n const apiBaseUrl = options?.apiBaseUrl ?? sdkContext?.apiBaseUrl;\n const apiKey = options?.apiKey ?? sdkContext?.apiKey;\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 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 }, [input.hintId, input.elationId, apiBaseUrl, apiKey, options?.skipRefresh]);\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 sdkContext = useTruthSdkContext();\n const apiBaseUrl = options?.apiBaseUrl ?? sdkContext?.apiBaseUrl;\n const apiKey = options?.apiKey ?? sdkContext?.apiKey;\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 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, apiBaseUrl, 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 /** Voicemail/call transcript — Dialpad delivers `{ text }` (B5). */\n transcript: unknown | null;\n /** AI-generated Dialpad recap summary (calls only; null for SMS). */\n recapSummary: string | null;\n text: string | null;\n mms: boolean;\n mmsUrl: string | null;\n messageStatus: string | null;\n /**\n * True while an outbound SMS is the optimistic local row inserted by the\n * Truth `sendMessage` path and not yet reconciled by the Dialpad webhook\n * (Truth #100). Clients gate a \"sending\" affordance on THIS flag — not on\n * `messageStatus === \"optimistic\"`, a value minted nowhere. Always `false`\n * for calls and reconciled rows.\n */\n optimistic: boolean;\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 * TruthProvider — single mount point for the Truth SDK on the frontend.\n *\n * - Connects to the correct Convex deployment for the given environment.\n * - Resolves the Truth REST API base URL + API key (env-var fallbacks\n * when no prop is passed) and exposes them via React context.\n * - Instantiates a shared `TruthClient` and exposes it via\n * `useTruthClient()` for React code and `getTruthClient()` for\n * non-React helpers (event handlers, lib modules).\n *\n * @example\n * ```tsx\n * import { TruthProvider } from '@hipnation-truth/sdk/react';\n *\n * function App() {\n * // Reads EXPO_PUBLIC_TRUTH_API_KEY automatically.\n * return (\n * <TruthProvider environment=\"uat\">\n * <MyApp />\n * </TruthProvider>\n * );\n * }\n * ```\n */\n\n\"use client\";\n\nimport { ConvexQueryClient } from \"@convex-dev/react-query\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { PersistQueryClientProvider } from \"@tanstack/react-query-persist-client\";\nimport { ConvexProvider, ConvexReactClient } from \"convex/react\";\nimport type { ReactNode } from \"react\";\nimport {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from \"react\";\nimport { TruthClient } from \"../client\";\nimport type { AuthTokenFetcher, Environment } from \"../types/config\";\nimport { SCHEMA_VERSION } from \"./offline/envelope\";\nimport { createOfflinePersister } from \"./offline/persister\";\nimport type { OfflineStore } from \"./offline/store\";\nimport { isNoopStore, NOOP_STORE } from \"./offline/store\";\n\n/**\n * How long a persisted query may rehydrate the UI before it's treated as\n * too stale to restore. Also used as the TanStack `gcTime` so inactive\n * queries survive in memory long enough to be persisted. 7 days balances\n * \"useful offline mirror\" against \"don't show month-old PHI\".\n */\nconst OFFLINE_MAX_AGE_MS = 1000 * 60 * 60 * 24 * 7;\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// Truth REST API base URL for each environment. Used by the\n// HTTP-backed SDK surfaces (device registration → Pinpoint, etc.)\n// which can't go through Convex directly because the underlying\n// AWS SDK calls live on the Lambda. Mirrors the Convex map: UAT +\n// production both hit the prod Lambda; everything else hits the\n// sandbox Lambda.\nexport const API_BASE_URLS: Record<string, string> = {\n local: \"https://app.sandbox.communication-hub.com\",\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 * 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\n/**\n * Resolve the Truth REST API base URL for a given environment. Mirrors\n * `resolveConvexUrl`. Honors an explicit override and falls back to\n * sandbox for unknown environments.\n */\nexport function resolveApiBaseUrl(\n environment: string | undefined,\n override?: string,\n): string {\n if (override) {\n return override;\n }\n const env = environment ?? \"sandbox\";\n return API_BASE_URLS[env] ?? API_BASE_URLS.sandbox;\n}\n\n/**\n * Read the env-var fallback for a value (e.g. `EXPO_PUBLIC_TRUTH_API_KEY`).\n * Cross-runtime safe — falls back to undefined when `process.env` is\n * unavailable (e.g. Vite without dotenv shim).\n */\nfunction readEnv(name: string): string | undefined {\n if (typeof process === \"undefined\" || !process.env) {\n return undefined;\n }\n const v = (process.env as Record<string, string | undefined>)[name];\n return typeof v === \"string\" && v.length > 0 ? v : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Context — exposes the Truth REST API base URL + API key + a shared\n// TruthClient to nested hooks. `useNotifications` (and any future\n// HTTP-backed hooks) read from here so CommHub doesn't have to pass\n// apiKey / apiBaseUrl to every hook call.\n// ---------------------------------------------------------------------------\n\ninterface TruthSdkContextValue {\n apiBaseUrl: string;\n apiKey: string;\n environment: string;\n client: TruthClient;\n /** Injected offline mirror (or the Noop default on web / flag off). */\n offlineStore: OfflineStore;\n /** Whether the offline-reads layer is active for this provider. */\n offlineEnabled: boolean;\n}\n\nconst TruthSdkContext = createContext<TruthSdkContextValue | null>(null);\n\n/**\n * Read the Truth REST API base URL + API key + shared client from\n * `TruthProvider`. Returns `null` when the provider isn't mounted,\n * which lets hooks fall back to explicit options for backwards\n * compatibility.\n */\nexport function useTruthSdkContext(): TruthSdkContextValue | null {\n return useContext(TruthSdkContext);\n}\n\n/**\n * Read the injected offline mirror. Returns the shared `NoopStore`\n * (transparent pass-through) when no provider is mounted or no store was\n * injected — so callers never have to null-check.\n */\nexport function useOfflineStore(): OfflineStore {\n return useContext(TruthSdkContext)?.offlineStore ?? NOOP_STORE;\n}\n\n/**\n * Whether the offline-reads layer is active. `false` when no provider is\n * mounted or the feature flag is off.\n */\nexport function useOfflineEnabled(): boolean {\n return useContext(TruthSdkContext)?.offlineEnabled ?? false;\n}\n\n/**\n * Hook variant of `getTruthClient()` for code that lives inside the\n * React tree. Returns the shared `TruthClient` instance from\n * `TruthProvider`. Throws if the provider isn't mounted.\n */\nexport function useTruthClient(): TruthClient {\n const ctx = useContext(TruthSdkContext);\n if (!ctx) {\n throw new Error(\n \"useTruthClient() called outside <TruthProvider>. Wrap your app in <TruthProvider> from @hipnation-truth/sdk/react.\",\n );\n }\n return ctx.client;\n}\n\n// ---------------------------------------------------------------------------\n// Singleton bridge for non-React helpers (lib/ modules, event-handler\n// closures). `TruthProvider` populates this on mount so callers can\n// `import { getTruthClient } from '@hipnation-truth/sdk/react'` without\n// needing to be inside the component tree.\n// ---------------------------------------------------------------------------\n\nlet _activeClient: TruthClient | null = null;\n\n/**\n * Return the shared `TruthClient` instance set by `<TruthProvider>`.\n * Throws if `TruthProvider` hasn't mounted yet — call this only from\n * code that runs after the provider has rendered (event handlers,\n * effects, async helpers invoked from rendered components).\n */\nexport function getTruthClient(): TruthClient {\n if (!_activeClient) {\n throw new Error(\n \"getTruthClient() called before <TruthProvider> mounted. Either wrap your app in <TruthProvider> or call useTruthClient() inside a component.\",\n );\n }\n return _activeClient;\n}\n\ninterface TruthProviderProps {\n /** Truth environment — determines which Convex + Lambda deployment to use */\n environment?: string;\n /** Override the Convex URL directly */\n convexUrl?: string;\n /**\n * Override the Truth REST API base URL directly. Optional — defaults\n * to the environment-mapped URL (`API_BASE_URLS[environment]`), then\n * falls back to `process.env.EXPO_PUBLIC_TRUTH_API_BASE_URL` if set.\n */\n apiBaseUrl?: string;\n /**\n * `hn_live_*` API key for the caller's application. Optional —\n * defaults to `process.env.EXPO_PUBLIC_TRUTH_API_KEY`. Stored in\n * React context + on the shared TruthClient, never logged.\n */\n apiKey?: string;\n /**\n * Optional source / tenant metadata for event tracking. Mirrors\n * `TruthClient` constructor options.\n */\n source?: string;\n sourceVersion?: string;\n tenantId?: string;\n /**\n * Per-call Clerk JWT fetcher (template \"convex\") identifying the\n * signed-in user to Convex. Wire it to your identity provider, e.g.\n * `useAuth().getToken({ template: \"convex\" })` from `@clerk/expo`.\n * Applied to both the live websocket client (React hooks) and the\n * shared `TruthClient` (resource methods). Identity changes are\n * picked up without remounting — the latest fetcher is read through\n * a ref on every call.\n */\n getAuthToken?: AuthTokenFetcher;\n /**\n * Synchronous encrypted KV mirror for durable offline reads, injected\n * by the consuming app (e.g. MMKV on `ch/`). Omit on web — the SDK\n * falls back to a `NoopStore` and the offline layer is a transparent\n * pass-through. Never import a native module into the SDK; inject it\n * here instead.\n */\n offlineStore?: OfflineStore;\n /**\n * Feature flag for the offline-reads layer. Default `false`. Has no\n * effect unless a real (non-Noop) `offlineStore` is also injected.\n * When both are set, the TanStack query cache is persisted to the\n * store so last-known reads survive a cold start with no network.\n */\n offlineEnabled?: boolean;\n children: ReactNode;\n}\n\nfunction TruthProvider({\n environment = \"sandbox\",\n convexUrl,\n apiBaseUrl,\n apiKey,\n source,\n sourceVersion,\n tenantId,\n getAuthToken,\n offlineStore = NOOP_STORE,\n offlineEnabled = false,\n children,\n}: TruthProviderProps) {\n const url = resolveConvexUrl(environment, convexUrl);\n // Resolve apiBaseUrl with this precedence: explicit prop →\n // EXPO_PUBLIC_TRUTH_API_BASE_URL env var → environment map.\n const resolvedApiBaseUrl =\n apiBaseUrl ??\n readEnv(\"EXPO_PUBLIC_TRUTH_API_BASE_URL\") ??\n resolveApiBaseUrl(environment);\n // Resolve apiKey with this precedence: explicit prop →\n // EXPO_PUBLIC_TRUTH_API_KEY env var.\n const resolvedApiKey = apiKey ?? readEnv(\"EXPO_PUBLIC_TRUTH_API_KEY\") ?? \"\";\n\n const convexClient = useMemo(() => new ConvexReactClient(url), [url]);\n\n // --- Caller identity (Clerk JWT) ------------------------------------\n //\n // Consumers typically pass an inline arrow for `getAuthToken`, whose\n // identity changes every render. Read it through a ref behind a\n // stable wrapper so the TruthClient + websocket auth don't churn.\n const getAuthTokenRef = useRef<AuthTokenFetcher | undefined>(getAuthToken);\n getAuthTokenRef.current = getAuthToken;\n const hasAuthFetcher = Boolean(getAuthToken);\n const stableGetAuthToken = useMemo<AuthTokenFetcher | undefined>(\n () =>\n hasAuthFetcher\n ? async () => (await getAuthTokenRef.current?.()) ?? null\n : undefined,\n [hasAuthFetcher],\n );\n\n // Authenticate the live websocket connection. `setAuth` re-invokes\n // the fetcher whenever the server reports an expired token, so the\n // 60s Clerk template lifetime never interrupts subscriptions.\n useEffect(() => {\n if (stableGetAuthToken) {\n convexClient.setAuth(async () => (await stableGetAuthToken()) ?? null);\n } else {\n convexClient.clearAuth();\n }\n }, [convexClient, stableGetAuthToken]);\n\n // --- Offline reads (Path A) ----------------------------------------\n //\n // Route Convex query subscriptions through TanStack Query so the\n // result set lives in a cache we can persist. The websocket still\n // drives live updates (live always wins); persistence only adds a\n // durable mirror. `usePersistentQuery` reads from this client.\n //\n // `gcTime` is multi-day so recently-used queries survive while\n // inactive long enough to be persisted/restored across a cold start.\n // The Convex integration manages `staleTime` per query, so we don't\n // set it globally.\n const convexQueryClient = useMemo(\n () => new ConvexQueryClient(convexClient),\n [convexClient],\n );\n const queryClient = useMemo(() => {\n return new QueryClient({\n defaultOptions: {\n queries: {\n queryKeyHashFn: convexQueryClient.hashFn(),\n queryFn: convexQueryClient.queryFn(),\n gcTime: OFFLINE_MAX_AGE_MS,\n // Convex throws on subscribe rather than returning errors;\n // retrying a permanently-failing query buys nothing.\n retry: false,\n },\n },\n });\n }, [convexQueryClient]);\n\n // `ConvexQueryClient.connect` must run exactly once per client — it\n // throws if called twice, and calling it as a side effect inside the\n // `useMemo` above risked re-running whenever React discarded the\n // memoized value. Wire it here, tracking the connected client by\n // identity so a new `convexQueryClient` (e.g. after a `url` change)\n // connects once while re-renders with the same instance are no-ops.\n const connectedClientRef = useRef<ConvexQueryClient | null>(null);\n useEffect(() => {\n if (connectedClientRef.current !== convexQueryClient) {\n convexQueryClient.connect(queryClient);\n connectedClientRef.current = convexQueryClient;\n }\n }, [convexQueryClient, queryClient]);\n\n // Persist only when the app both injected a real store AND opted in.\n // On web (Noop store) or with the flag off this stays undefined and we\n // mount a plain QueryClientProvider — no persistence, behavior intact.\n const persister = useMemo(\n () =>\n offlineEnabled && !isNoopStore(offlineStore)\n ? createOfflinePersister(offlineStore)\n : undefined,\n [offlineEnabled, offlineStore],\n );\n\n const truthClient = useMemo(\n () =>\n new TruthClient({\n // Pin the resource client to the SAME deployment the React hooks\n // use; without this it falls back to CONVEX_URLS[environment] and a\n // caller-supplied `convexUrl` would split hooks vs. client methods.\n convexUrl: url,\n apiKey: resolvedApiKey,\n environment: environment as Environment,\n apiBaseUrl: resolvedApiBaseUrl,\n source: source ?? \"unknown\",\n sourceVersion: sourceVersion ?? \"unknown\",\n tenantId: tenantId ?? \"\",\n autoInitServiceWorker: false,\n getAuthToken: stableGetAuthToken,\n }),\n [\n url,\n resolvedApiKey,\n resolvedApiBaseUrl,\n environment,\n source,\n sourceVersion,\n tenantId,\n stableGetAuthToken,\n ],\n );\n\n // Populate the non-React singleton bridge so helpers that import\n // `getTruthClient` from the SDK directly resolve to the same\n // instance the React tree is using.\n useEffect(() => {\n _activeClient = truthClient;\n return () => {\n if (_activeClient === truthClient) {\n _activeClient = null;\n }\n // Tear down the prior client's tracker lifecycle so a config change /\n // remount doesn't leak duplicate background event delivery.\n void truthClient.destroy().catch(() => {\n /* best-effort cleanup */\n });\n };\n }, [truthClient]);\n\n const sdkContext = useMemo<TruthSdkContextValue>(\n () => ({\n apiBaseUrl: resolvedApiBaseUrl,\n apiKey: resolvedApiKey,\n environment,\n client: truthClient,\n offlineStore,\n offlineEnabled,\n }),\n [\n resolvedApiBaseUrl,\n resolvedApiKey,\n environment,\n truthClient,\n offlineStore,\n offlineEnabled,\n ],\n );\n\n // `ConvexProvider` stays innermost so `convex/react` hooks (mutations,\n // any non-wrapped reads) keep working. The TanStack provider wraps it:\n // `PersistQueryClientProvider` when a persister exists, otherwise a\n // plain `QueryClientProvider`.\n const convexTree = createElement(\n ConvexProvider,\n { client: convexClient },\n children,\n );\n const queryTree = persister\n ? createElement(\n PersistQueryClientProvider,\n {\n client: queryClient,\n persistOptions: {\n persister,\n maxAge: OFFLINE_MAX_AGE_MS,\n // A SCHEMA_VERSION bump busts the entire dehydrated cache, so\n // an older build's shapes can never rehydrate the new UI.\n buster: String(SCHEMA_VERSION),\n },\n },\n convexTree,\n )\n : createElement(QueryClientProvider, { client: queryClient }, convexTree);\n\n return createElement(\n TruthSdkContext.Provider,\n { value: sdkContext },\n queryTree,\n );\n}\n\nexport { TruthProvider };\nexport type { TruthProviderProps };\n","/**\n * AuthAwareConvexHttpClient — ConvexHttpClient that refreshes the\n * caller's identity token before every call.\n *\n * `ConvexHttpClient.setAuth()` takes a static JWT, but Clerk's \"convex\"\n * template tokens are short-lived (60s). This subclass asks the\n * injected {@link AuthTokenFetcher} for a current token immediately\n * before each query/mutation/action — identity-provider SDKs (Clerk)\n * cache tokens internally and refresh on expiry, so this is cheap.\n *\n * With no fetcher configured it behaves exactly like the base client.\n */\nimport { ConvexHttpClient, type HttpMutationOptions } from \"convex/browser\";\nimport type {\n ArgsAndOptions,\n FunctionReference,\n FunctionReturnType,\n OptionalRestArgs,\n} from \"convex/server\";\nimport type { AuthTokenFetcher } from \"./types/config\";\n\nexport class AuthAwareConvexHttpClient extends ConvexHttpClient {\n private readonly getAuthToken?: AuthTokenFetcher;\n\n constructor(address: string, getAuthToken?: AuthTokenFetcher) {\n super(address);\n this.getAuthToken = getAuthToken;\n }\n\n /**\n * Pull a fresh token from the fetcher and apply it. A fetcher error\n * keeps the previously-applied token rather than dropping auth\n * mid-session.\n */\n private async syncAuth(): Promise<void> {\n if (!this.getAuthToken) {\n return;\n }\n try {\n const token = await this.getAuthToken();\n if (token) {\n this.setAuth(token);\n } else {\n this.clearAuth();\n }\n } catch {\n /* keep last known token */\n }\n }\n\n override async query<Query extends FunctionReference<\"query\">>(\n query: Query,\n ...args: OptionalRestArgs<Query>\n ): Promise<FunctionReturnType<Query>> {\n await this.syncAuth();\n return super.query(query, ...args);\n }\n\n override async mutation<Mutation extends FunctionReference<\"mutation\">>(\n mutation: Mutation,\n ...args: ArgsAndOptions<Mutation, HttpMutationOptions>\n ): Promise<FunctionReturnType<Mutation>> {\n await this.syncAuth();\n return super.mutation(mutation, ...args);\n }\n\n override async action<Action extends FunctionReference<\"action\">>(\n action: Action,\n ...args: OptionalRestArgs<Action>\n ): Promise<FunctionReturnType<Action>> {\n await this.syncAuth();\n return super.action(action, ...args);\n }\n}\n","/**\n * AppointmentResource provides data access to normalized appointment records\n * backed by Convex.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type { Appointment, AppointmentListOptions } from \"../types/appointment\";\nimport type { PaginatedResult } from \"../types/config\";\n\nclass AppointmentResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convexClient: ConvexHttpClient) {\n this.convex = convexClient;\n }\n\n /**\n * Get an appointment by its Truth platform ID.\n */\n async get(id: string): Promise<Appointment | null> {\n try {\n const result = await this.convex.query(\n \"appointments:getById\" as never,\n { id } as never,\n );\n return (result as Appointment) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * List appointments with optional filters, pagination, and limit.\n */\n async list(\n options?: AppointmentListOptions,\n ): Promise<PaginatedResult<Appointment>> {\n try {\n const result = await this.convex.query(\n \"appointments:list\" as never,\n {\n patientId: options?.patientId,\n startDate: options?.startDate,\n endDate: options?.endDate,\n status: options?.status,\n limit: options?.limit,\n cursor: options?.cursor,\n } as never,\n );\n\n const typed = result as PaginatedResult<Appointment> | undefined;\n\n return typed ?? { data: [], cursor: null, hasMore: false };\n } catch {\n return { data: [], cursor: null, hasMore: false };\n }\n }\n}\n\nexport { AppointmentResource };\n","/**\n * AttachmentsResource — presigned S3 upload/download + Convex metadata.\n *\n * Replaces CommHub's base64-in-Postgres attachment flow:\n * 1. client.attachments.createUploadUrl(...) → presigned PUT URL\n * 2. Client PUTs bytes directly to S3\n * 3. client.attachments.record(...) → writes Convex metadata\n * 4. client.attachments.getDownloadUrl(s3Key) → presigned GET URL\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\n\nexport interface CreateUploadUrlInput {\n fileName: string;\n mimeType: string;\n size: number;\n conversationId?: string;\n}\n\nexport interface CreateUploadUrlResult {\n uploadUrl: string;\n s3Key: string;\n expiresIn: number;\n applicationId: string | null;\n}\n\nexport interface GetDownloadUrlResult {\n url: string;\n expiresIn: number;\n}\n\nexport interface RecordAttachmentInput {\n s3Key: string;\n fileName: string;\n mimeType: string;\n size: number;\n conversationId?: string;\n uploadedBy: string;\n}\n\nexport interface Attachment {\n _id: string;\n s3Key: string;\n fileName: string;\n mimeType: string;\n size: number;\n conversationId?: string;\n uploadedBy: string;\n createdAt: string;\n}\n\nclass AttachmentsError extends Error {\n readonly status: number;\n\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Attachment ${operation} failed (HTTP ${status})`);\n this.name = \"AttachmentsError\";\n this.status = status;\n }\n}\n\nclass AttachmentsResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly convex: ConvexHttpClient;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n convexClient: ConvexHttpClient,\n ) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n this.convex = convexClient;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new AttachmentsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n async createUploadUrl(\n input: CreateUploadUrlInput,\n ): Promise<CreateUploadUrlResult> {\n return await this.post<CreateUploadUrlResult>(\n \"/attachments/upload-url\",\n input,\n );\n }\n\n async getDownloadUrl(\n s3Key: string,\n expiresIn?: number,\n ): Promise<GetDownloadUrlResult> {\n return await this.post<GetDownloadUrlResult>(\"/attachments/download-url\", {\n s3Key,\n expiresIn,\n });\n }\n\n async record(\n input: RecordAttachmentInput,\n ): Promise<{ attachmentId: string; s3Key: string }> {\n return (await this.convex.mutation(\n \"attachments:record\" as never,\n input as never,\n )) as { attachmentId: string; s3Key: string };\n }\n\n async get(attachmentId: string): Promise<Attachment | null> {\n try {\n const row = (await this.convex.query(\n \"attachments:getById\" as never,\n { attachmentId } as never,\n )) as Attachment | null;\n return row ?? null;\n } catch {\n return null;\n }\n }\n\n async listByConversation(conversationId: string): Promise<Attachment[]> {\n try {\n const rows = (await this.convex.query(\n \"attachments:listByConversation\" as never,\n { conversationId } as never,\n )) as Attachment[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n\n /**\n * One-shot upload: presign → PUT to S3 → record in Convex → return a\n * 7-day signed download URL ready to embed in an outbound SMS. Caller\n * passes the resulting `downloadUrl` to `messages.dialpad.sendSms` (or\n * the new `messages.sendAttachmentMessage`) to actually deliver it.\n *\n * Replaces CommHub's NestJS `/send-attachment` controller in a single\n * SDK call — no base64 round-trip, no legacy `/attachments/:id/download`\n * REST endpoint required.\n */\n async upload(input: {\n file: Blob | ArrayBuffer | Uint8Array;\n fileName: string;\n mimeType: string;\n size: number;\n conversationId?: string;\n uploadedBy: string;\n /** Download URL TTL in seconds. Default 7 days. */\n downloadExpiresIn?: number;\n }): Promise<{\n attachmentId: string;\n s3Key: string;\n downloadUrl: string;\n }> {\n const presigned = await this.createUploadUrl({\n fileName: input.fileName,\n mimeType: input.mimeType,\n size: input.size,\n conversationId: input.conversationId,\n });\n\n // Cast through `BodyInit` — Uint8Array is accepted by undici/fetch\n // at runtime but the lib.dom types only declare BufferSource so TS\n // doesn't see the overload. Same trick the AttachmentsResource\n // already uses elsewhere.\n const body: BodyInit =\n input.file instanceof Blob\n ? input.file\n : input.file instanceof Uint8Array\n ? (input.file as unknown as BodyInit)\n : (new Uint8Array(input.file) as unknown as BodyInit);\n\n // 30s timeout matches the legacy NestJS handler so a stuck S3 PUT\n // can't hang the calling UI thread indefinitely.\n const abort = new AbortController();\n const timer = setTimeout(() => abort.abort(), 30_000);\n let putRes: Response;\n try {\n putRes = await fetch(presigned.uploadUrl, {\n method: \"PUT\",\n headers: { \"Content-Type\": input.mimeType },\n body,\n signal: abort.signal,\n });\n } catch (err) {\n // AbortController fires DOMException(\"AbortError\"); rewrap as a\n // typed AttachmentsError(0, …) so callers' `instanceof\n // AttachmentsError` checks catch the timeout consistently with\n // every other failure mode here.\n const isAbort =\n err instanceof Error &&\n (err.name === \"AbortError\" || err.name === \"TimeoutError\");\n if (isAbort) {\n throw new AttachmentsError(\n \"s3-put\",\n 0,\n \"S3 upload timed out after 30s\",\n );\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n if (!putRes.ok) {\n throw new AttachmentsError(\n \"s3-put\",\n putRes.status,\n `S3 PUT ${putRes.status} for ${presigned.s3Key}`,\n );\n }\n\n const recorded = await this.record({\n s3Key: presigned.s3Key,\n fileName: input.fileName,\n mimeType: input.mimeType,\n size: input.size,\n conversationId: input.conversationId,\n uploadedBy: input.uploadedBy,\n });\n\n const signed = await this.getDownloadUrl(\n presigned.s3Key,\n input.downloadExpiresIn ?? 7 * 24 * 3600,\n );\n\n return {\n attachmentId: recorded.attachmentId,\n s3Key: presigned.s3Key,\n downloadUrl: signed.url,\n };\n }\n}\n\nexport { AttachmentsResource, AttachmentsError };\n","/**\n * ConversationsResource — write methods that hang off a specific\n * conversation: notes, tasks, outbound messages.\n *\n * Replaces CommHub's `truthConversationApi.ts` raw-fetch wrappers so\n * the frontend goes through a typed SDK surface instead of building\n * URLs by hand.\n *\n * All methods proxy the matching oRPC procedures (`/conversations/*`)\n * via Truth's application-key auth. Errors surface as\n * `ConversationsError` with a status code so callers can distinguish\n * transport failures (status=0) from API rejections (status>=400).\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Address a conversation by either Convex `_id` or the phonePair. */\nexport interface ConversationAddress {\n conversationId?: string;\n phonePair?: string;\n}\n\nexport type ConversationTaskStatus = \"pending\" | \"completed\";\nexport type ConversationTaskPriority = \"high\" | \"medium\" | \"low\";\n\nexport interface CreateConversationNoteInput extends ConversationAddress {\n eventId?: string;\n author: string;\n content: string;\n status?: string;\n type?: string;\n}\n\nexport interface CreateConversationTaskInput extends ConversationAddress {\n eventId?: string;\n author: string;\n /** Optional short headline, distinct from `description`. */\n title?: string;\n description: string;\n status?: ConversationTaskStatus;\n priority?: ConversationTaskPriority;\n assignee?: string;\n type?: string;\n}\n\nexport interface SetConversationTaskStatusInput {\n taskId: string;\n status: ConversationTaskStatus;\n resolvedBy?: string;\n /**\n * Email of the user performing the action. Truth excludes the actor\n * from the task-action push fan-out (legacy parity) — omit it and the\n * actor notifies themselves.\n */\n actor?: string;\n}\n\nexport interface UpdateConversationTaskInput {\n taskId: string;\n /** Conversation the task belongs to. Truth requires this on PATCH. */\n conversationId: string;\n /** Required by the Truth task router — see `messages/procedures/tasks.ts`. */\n author: string;\n description: string;\n /** Optional patch fields — pass only the ones you want to change. */\n title?: string;\n priority?: ConversationTaskPriority;\n status?: ConversationTaskStatus;\n assignee?: string;\n type?: string;\n /**\n * Email of the user performing the action. Truth excludes the actor\n * from the task-action push fan-out (legacy parity) — omit it and the\n * actor notifies themselves.\n */\n actor?: string;\n}\n\nexport interface SetConversationTaskArchivedInput {\n taskId: string;\n archived: boolean;\n /** Email of the acting user — excluded from the archive push fan-out. */\n actor?: string;\n}\n\nexport interface SendConversationMessageInput {\n fromNumber: string;\n toNumber: string;\n message?: string;\n media?: string;\n}\n\nexport interface SendConversationMessageResult {\n id: number;\n messageStatus: string;\n direction: string;\n}\n\nexport class ConversationsError extends Error {\n readonly status: number;\n\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Conversations ${operation} failed (HTTP ${status})`);\n this.name = \"ConversationsError\";\n this.status = status;\n }\n}\n\n/**\n * Guard: a conversation must be addressed by either `conversationId` or\n * `phonePair`. Reject locally so callers get an actionable error\n * instead of an avoidable server round-trip.\n */\nfunction assertConversationAddress(\n operation: string,\n input: ConversationAddress,\n): void {\n if (!input.conversationId && !input.phonePair) {\n throw new ConversationsError(\n operation,\n 0,\n \"Either `conversationId` or `phonePair` is required\",\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Subresources\n// ---------------------------------------------------------------------------\n\nclass ConversationNotesSubresource {\n constructor(\n private readonly post: <T>(path: string, body: unknown) => Promise<T>,\n ) {}\n\n /** Create a note on a conversation (addressed by id or phonePair). */\n async create(input: CreateConversationNoteInput): Promise<{ id: string }> {\n assertConversationAddress(\"notes.create\", input);\n return this.post<{ id: string }>(\"/conversations/notes\", input);\n }\n}\n\nclass ConversationTasksSubresource {\n constructor(\n private readonly post: <T>(path: string, body: unknown) => Promise<T>,\n private readonly patch: <T>(path: string, body: unknown) => Promise<T>,\n ) {}\n\n /** Create a task on a conversation. */\n async create(input: CreateConversationTaskInput): Promise<{ id: string }> {\n assertConversationAddress(\"tasks.create\", input);\n return this.post<{ id: string }>(\"/conversations/tasks\", input);\n }\n\n /** Mark a task pending or completed. */\n async setStatus(input: SetConversationTaskStatusInput): Promise<{\n ok: true;\n }> {\n return this.post<{ ok: true }>(\n `/conversations/tasks/${encodeURIComponent(input.taskId)}/status`,\n {\n id: input.taskId,\n status: input.status,\n ...(input.resolvedBy ? { resolvedBy: input.resolvedBy } : {}),\n ...(input.actor ? { actor: input.actor } : {}),\n },\n );\n }\n\n /** Archive or un-archive a task (hides it from My Tasks without deleting). */\n async setArchived(input: SetConversationTaskArchivedInput): Promise<{\n ok: true;\n changed: boolean;\n }> {\n return this.post<{ ok: true; changed: boolean }>(\n `/conversations/tasks/${encodeURIComponent(input.taskId)}/archive`,\n {\n id: input.taskId,\n archived: input.archived,\n ...(input.actor ? { actor: input.actor } : {}),\n },\n );\n }\n\n /**\n * Update task fields (priority, assignee, description, type). Wraps\n * `PATCH /api/conversations/tasks/{id}` so CommHub doesn't have to\n * direct-fetch for non-status field edits.\n */\n async update(input: UpdateConversationTaskInput): Promise<{ ok: true }> {\n return this.patch<{ ok: true }>(\n `/conversations/tasks/${encodeURIComponent(input.taskId)}`,\n {\n id: input.taskId,\n conversationId: input.conversationId,\n author: input.author,\n description: input.description,\n ...(input.title !== undefined ? { title: input.title } : {}),\n ...(input.priority ? { priority: input.priority } : {}),\n ...(input.status ? { status: input.status } : {}),\n ...(input.assignee !== undefined ? { assignee: input.assignee } : {}),\n ...(input.type !== undefined ? { type: input.type } : {}),\n ...(input.actor ? { actor: input.actor } : {}),\n },\n );\n }\n}\n\nclass ConversationMessagesSubresource {\n constructor(\n private readonly post: <T>(path: string, body: unknown) => Promise<T>,\n ) {}\n\n /**\n * Send an outbound SMS / MMS via the typed `sendMessage` oRPC procedure.\n *\n * Prefer this over the generic Dialpad proxy (`messages.dialpad.sendSms`)\n * — this hits the validated Truth route and consistently surfaces\n * `ConversationsError` on failure.\n */\n async send(\n input: SendConversationMessageInput,\n ): Promise<SendConversationMessageResult> {\n if (!input.message && !input.media) {\n throw new ConversationsError(\n \"messages.send\",\n 0,\n \"send requires `message` or `media`\",\n );\n }\n return this.post<SendConversationMessageResult>(\n \"/conversations/messages\",\n input,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// ConversationsResource\n// ---------------------------------------------------------------------------\n\nexport class ConversationsResource {\n readonly notes: ConversationNotesSubresource;\n readonly tasks: ConversationTasksSubresource;\n readonly messages: ConversationMessagesSubresource;\n\n /** 30s upstream timeout — matches NotesResource for consistency. */\n private static readonly REQUEST_TIMEOUT_MS = 30_000;\n\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly convex: import(\"convex/browser\").ConvexHttpClient | null;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n convex?: import(\"convex/browser\").ConvexHttpClient,\n ) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n this.convex = convex ?? null;\n const post = <T>(path: string, body: unknown) =>\n this.postRequest<T>(path, body);\n const patch = <T>(path: string, body: unknown) =>\n this.patchRequest<T>(path, body);\n this.notes = new ConversationNotesSubresource(post);\n this.tasks = new ConversationTasksSubresource(post, patch);\n this.messages = new ConversationMessagesSubresource(post);\n }\n\n /**\n * Mark a conversation read for the calling user (zeroes unreadCount,\n * stamps `lastReadAt`). Calls the public Convex `markRead` mutation\n * which enforces self-tenancy on the auth identity.\n */\n async markRead(input: {\n conversationId: string;\n userId: string;\n }): Promise<{ ok: true }> {\n if (!this.convex) {\n throw new ConversationsError(\n \"/markRead\",\n 0,\n \"ConversationsResource missing Convex client\",\n );\n }\n return (await this.convex.mutation(\n \"conversations:markRead\" as never,\n input as never,\n )) as { ok: true };\n }\n\n /**\n * Mark a conversation unread for the calling user (sets unreadCount=1).\n */\n async markUnread(input: {\n conversationId: string;\n userId: string;\n }): Promise<{ updated: true }> {\n if (!this.convex) {\n throw new ConversationsError(\n \"/markUnread\",\n 0,\n \"ConversationsResource missing Convex client\",\n );\n }\n return (await this.convex.mutation(\n \"conversations:markUnread\" as never,\n input as never,\n )) as { updated: true };\n }\n\n /**\n * Zero unread on every conversation the user has reads for, except\n * those whose `providerPhone` is in `excludedProviderPhones`.\n */\n async clearAllUnread(input: {\n userId: string;\n excludedProviderPhones?: string[];\n }): Promise<{ cleared: number }> {\n if (!this.convex) {\n throw new ConversationsError(\n \"/clearAllUnread\",\n 0,\n \"ConversationsResource missing Convex client\",\n );\n }\n return (await this.convex.mutation(\n \"conversations:clearAllUnread\" as never,\n input as never,\n )) as { cleared: number };\n }\n\n private async postRequest<T>(path: string, body: unknown): Promise<T> {\n if (!this.apiKey) {\n throw new ConversationsError(\n path,\n 0,\n \"Truth API key not configured — request blocked\",\n );\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(\n () => controller.abort(),\n ConversationsResource.REQUEST_TIMEOUT_MS,\n );\n\n let res: Response;\n try {\n res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n const isAbort =\n err instanceof Error &&\n (err.name === \"AbortError\" || err.name === \"TimeoutError\");\n const message = isAbort\n ? `Conversations ${path} timed out after ${ConversationsResource.REQUEST_TIMEOUT_MS}ms`\n : err instanceof Error\n ? err.message\n : \"Conversations request failed before response\";\n throw new ConversationsError(path, 0, message);\n } finally {\n clearTimeout(timeout);\n }\n\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new ConversationsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n /**\n * PATCH variant of `postRequest`. Mirrors the timeout + API-key\n * handling so callers like `tasks.update()` don't need to roll their\n * own fetch. The Truth task router treats unknown methods as 405, so\n * a dedicated PATCH path is required.\n */\n private async patchRequest<T>(path: string, body: unknown): Promise<T> {\n if (!this.apiKey) {\n throw new ConversationsError(\n path,\n 0,\n \"Truth API key not configured — request blocked\",\n );\n }\n const controller = new AbortController();\n const timeout = setTimeout(\n () => controller.abort(),\n ConversationsResource.REQUEST_TIMEOUT_MS,\n );\n let res: Response;\n try {\n res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n const isAbort =\n err instanceof Error &&\n (err.name === \"AbortError\" || err.name === \"TimeoutError\");\n const message = isAbort\n ? `Conversations ${path} timed out after ${ConversationsResource.REQUEST_TIMEOUT_MS}ms`\n : err instanceof Error\n ? err.message\n : String(err);\n throw new ConversationsError(path, 0, message);\n } finally {\n clearTimeout(timeout);\n }\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new ConversationsError(\n path,\n res.status,\n `Conversations ${path} failed: ${text.slice(0, 200)}`,\n );\n }\n return (await res.json()) as T;\n }\n}\n","/**\n * Dialpad resource — typed access to Truth's Dialpad proxy endpoints.\n *\n * @example\n * ```ts\n * const sms = await truth.messages.dialpad.sendSms({ from_number, to_number, message });\n * const call = await truth.messages.dialpad.initiateCall(userId, phoneNumber);\n * await truth.messages.dialpad.hangupCall(callId);\n * const url = await truth.messages.dialpad.authenticateVoicemail(voicemailLink);\n * ```\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface SendSmsParams {\n from_number: string;\n to_number: string;\n message?: string;\n media?: string | string[];\n}\n\ninterface SendSmsResponse {\n id: string;\n message_status: string;\n [key: string]: unknown;\n}\n\ninterface InitiateCallResponse {\n call_id: number;\n [key: string]: unknown;\n}\n\ninterface CallStatusResponse {\n state: string;\n [key: string]: unknown;\n}\n\ninterface DialpadUser {\n id: number;\n emails: string[];\n first_name?: string;\n last_name?: string;\n [key: string]: unknown;\n}\n\ninterface DialpadUserListResponse {\n items: DialpadUser[];\n}\n\ninterface DialpadNumberInfo {\n user_id?: number;\n type?: string;\n [key: string]: unknown;\n}\n\ninterface VoicemailAuthResponse {\n success: boolean;\n authenticated_url: string | null;\n error?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Dialpad Resource\n// ---------------------------------------------------------------------------\n\nclass DialpadResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(apiBaseUrl: string, apiKey: string) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n }\n\n /**\n * Send an SMS or MMS message via Dialpad.\n */\n async sendSms(params: SendSmsParams): Promise<SendSmsResponse> {\n const body = {\n to_numbers: [params.to_number],\n from_number: params.from_number,\n infer_country_code: false,\n ...(params.message ? { text: params.message } : {}),\n ...(params.media ? { media: params.media } : {}),\n };\n\n return this.post<SendSmsResponse>(\"/sms\", body);\n }\n\n /**\n * Initiate an outbound call from a Dialpad user to a phone number.\n */\n async initiateCall(\n userId: number,\n phoneNumber: string,\n ): Promise<InitiateCallResponse> {\n return this.post<InitiateCallResponse>(`/users/${userId}/initiate_call`, {\n phone_number: phoneNumber,\n });\n }\n\n /**\n * Hang up an active call.\n */\n async hangupCall(callId: number): Promise<void> {\n await this.put(`/call/${callId}/actions/hangup`);\n }\n\n /**\n * Alias for `hangupCall` — mirrors the CommHub `endCall` action name so\n * the SDK swap is a direct rename.\n */\n async endCall(callId: number): Promise<void> {\n await this.hangupCall(callId);\n }\n\n /**\n * Send an MMS with a pre-uploaded attachment. Takes the S3 key returned\n * by `client.attachments.createUploadUrl(...)` + PUT, fetches a short-\n * lived download URL, and hands it to Dialpad as `media[]`.\n *\n * Replaces CommHub's `sendAttachment` Hasura Action (which stored bytes\n * as base64 in Postgres and served them through a GET endpoint).\n */\n async sendAttachmentWithUrl(params: {\n from_number: string;\n to_number: string;\n mediaUrl: string;\n message?: string;\n }): Promise<SendSmsResponse> {\n return this.sendSms({\n from_number: params.from_number,\n to_number: params.to_number,\n message: params.message,\n media: [params.mediaUrl],\n });\n }\n\n /**\n * Get the status of a call.\n */\n async getCallStatus(callId: number): Promise<CallStatusResponse | null> {\n try {\n return await this.get<CallStatusResponse>(`/call/${callId}`);\n } catch (error) {\n if (error instanceof DialpadProxyError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Get a Dialpad user by their user ID.\n */\n async getUser(userId: string | number): Promise<DialpadUser | null> {\n try {\n return await this.get<DialpadUser>(`/users/${userId}`);\n } catch (error) {\n if (error instanceof DialpadProxyError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Find a Dialpad user by email.\n */\n async getUserByEmail(email: string): Promise<DialpadUser | null> {\n const result = await this.get<DialpadUserListResponse>(\"/users\", {\n email,\n });\n return result.items?.[0] ?? null;\n }\n\n /**\n * Find a Dialpad user by phone number.\n */\n async getUserByPhoneNumber(phoneNumber: string): Promise<DialpadUser | null> {\n const result = await this.get<DialpadUserListResponse>(\"/users\", {\n number: phoneNumber,\n });\n return result.items?.[0] ?? null;\n }\n\n /**\n * Get information about a Dialpad phone number.\n */\n async getNumberInfo(phoneNumber: string): Promise<DialpadNumberInfo | null> {\n try {\n const cleanNumber = phoneNumber.replace(/[^\\d+]/g, \"\");\n return await this.get<DialpadNumberInfo>(\n `/numbers/${encodeURIComponent(cleanNumber)}`,\n );\n } catch (error) {\n if (error instanceof DialpadProxyError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Authenticate a voicemail download URL. Truth appends the Dialpad API key,\n * follows redirects, and returns the clean URL for client-side playback.\n */\n async authenticateVoicemail(voicemailLink: string): Promise<string | null> {\n const url = `${this.baseUrl}/api/messages/dialpad/voicemail/authenticate`;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify({ voicemail_link: voicemailLink }),\n });\n\n const result = (await response.json()) as VoicemailAuthResponse;\n\n if (!response.ok || !result.success) {\n return null;\n }\n\n return result.authenticated_url;\n }\n\n // -----------------------------------------------------------------------\n // Internal HTTP helpers\n // -----------------------------------------------------------------------\n\n private async get<T>(\n path: string,\n params?: Record<string, unknown>,\n ): Promise<T> {\n const url = new URL(`/api/messages/dialpad${path}`, this.baseUrl);\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const response = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n });\n\n if (!response.ok) {\n throw new DialpadProxyError(\"GET\", path, response.status);\n }\n\n return (await response.json()) as T;\n }\n\n private async post<T>(path: string, body?: unknown): Promise<T> {\n const url = `${this.baseUrl}/api/messages/dialpad${path}`;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n throw new DialpadProxyError(\"POST\", path, response.status);\n }\n\n return (await response.json()) as T;\n }\n\n private async put<T = void>(path: string, body?: unknown): Promise<T> {\n const url = `${this.baseUrl}/api/messages/dialpad${path}`;\n\n const response = await fetch(url, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n throw new DialpadProxyError(\"PUT\", path, response.status);\n }\n\n if (response.headers.get(\"content-type\")?.includes(\"json\")) {\n return (await response.json()) as T;\n }\n\n return undefined as T;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Messages Resource — namespace for messaging providers\n// ---------------------------------------------------------------------------\n\nclass MessagesResource {\n readonly dialpad: DialpadResource;\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(apiBaseUrl: string, apiKey: string) {\n this.dialpad = new DialpadResource(apiBaseUrl, apiKey);\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n }\n\n /**\n * Get an authenticated URL for a Dialpad voicemail recording.\n *\n * Replaces CommHub's `getAuthenticatedVoicemailUrl` Hasura mutation\n * (which proxied to the legacy NestJS backend). Truth appends the\n * Dialpad API key, follows redirects, strips the key from the final\n * URL, and returns it for client-side audio playback.\n *\n * @param voicemailLink The raw Dialpad voicemail download URL (value\n * of the `voicemail_link` field on the call event row).\n * @returns An object with `url` (clean playback URL) and `expiresAt`\n * (ISO-8601 timestamp, ~5 min from request time, informational only).\n */\n async getVoicemailUrl(voicemailLink: string): Promise<{\n url: string;\n expiresAt: string;\n }> {\n const res = await fetch(`${this.baseUrl}/api/conversations/voicemail/url`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify({ voicemailLink }),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `messages.getVoicemailUrl failed (HTTP ${res.status}): ${text.slice(0, 200)}`,\n );\n }\n return (await res.json()) as { url: string; expiresAt: string };\n }\n\n /**\n * End a Dialpad call via the typed oRPC procedure (POST hangup, with\n * 404→`alreadyEnded` so the UI doesn't flash an error on a natural\n * race). Replaces CommHub's `useEndCallMutation` Hasura action.\n *\n * Prefer this over `messages.dialpad.endCall` which goes through the\n * generic proxy (PUT, no 404 handling).\n */\n async endCall(callId: number | string): Promise<{\n ok: true;\n alreadyEnded: boolean;\n }> {\n const res = await fetch(`${this.baseUrl}/api/conversations/calls/end`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify({ callId }),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `messages.endCall failed (HTTP ${res.status}): ${text.slice(0, 200)}`,\n );\n }\n return (await res.json()) as { ok: true; alreadyEnded: boolean };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Error\n// ---------------------------------------------------------------------------\n\nclass DialpadProxyError extends Error {\n readonly method: string;\n readonly path: string;\n readonly status: number;\n\n constructor(method: string, path: string, status: number) {\n super(\n `Dialpad proxy error: ${method} /api/messages/dialpad${path} returned ${status}`,\n );\n this.name = \"DialpadProxyError\";\n this.method = method;\n this.path = path;\n this.status = status;\n }\n}\n\nexport { DialpadResource, DialpadProxyError, MessagesResource };\nexport type {\n SendSmsParams,\n SendSmsResponse,\n InitiateCallResponse,\n CallStatusResponse,\n DialpadUser,\n DialpadNumberInfo,\n VoicemailAuthResponse,\n};\n","/**\n * EHR proxy resource — typed access to Truth's EHR proxy endpoints.\n *\n * Provides per-provider proxy methods so consumers never construct\n * proxy URLs manually. Every request carries the application's\n * `X-API-Key` — Truth's API gate rejects unauthenticated callers.\n *\n * @example\n * ```ts\n * const patient = await truth.ehr.elation.get('/patients/123/');\n * const notes = await truth.ehr.elation.post('/non_visit_notes/', noteData);\n * const hintPatient = await truth.ehr.hint.get('/provider/patients/456');\n * ```\n */\n\n// ---------------------------------------------------------------------------\n// Provider proxy\n// ---------------------------------------------------------------------------\n\nclass EhrProviderProxy {\n private readonly baseUrl: string;\n private readonly provider: string;\n private readonly apiKey: string;\n\n constructor(apiBaseUrl: string, provider: string, apiKey: string) {\n this.baseUrl = apiBaseUrl;\n this.provider = provider;\n this.apiKey = apiKey;\n }\n\n private headers(withBody: boolean): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n };\n if (withBody) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n return headers;\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n params?: Record<string, unknown>,\n ): Promise<T> {\n const url = new URL(`/api/ehr/${this.provider}${path}`, this.baseUrl);\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const response = await fetch(url.toString(), {\n method,\n headers: this.headers(body !== undefined),\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n throw new EhrProxyError(this.provider, method, path, response.status);\n }\n\n return (await response.json()) as T;\n }\n\n /**\n * GET request to the EHR proxy.\n * @param path — path relative to the provider root (e.g., \"/patients/123/\")\n * @param params — optional query parameters\n */\n async get<T = unknown>(\n path: string,\n params?: Record<string, unknown>,\n ): Promise<T> {\n return this.request<T>(\"GET\", path, undefined, params);\n }\n\n /** POST request to the EHR proxy. */\n async post<T = unknown>(path: string, body?: unknown): Promise<T> {\n return this.request<T>(\"POST\", path, body);\n }\n\n /** PUT request to the EHR proxy. */\n async put<T = unknown>(path: string, body?: unknown): Promise<T> {\n return this.request<T>(\"PUT\", path, body);\n }\n\n /** PATCH request to the EHR proxy. */\n async patch<T = unknown>(path: string, body?: unknown): Promise<T> {\n return this.request<T>(\"PATCH\", path, body);\n }\n\n /** DELETE request to the EHR proxy. */\n async delete<T = unknown>(path: string): Promise<T> {\n return this.request<T>(\"DELETE\", path);\n }\n}\n\n// ---------------------------------------------------------------------------\n// EHR Resource — exposes per-provider proxies\n// ---------------------------------------------------------------------------\n\nclass EhrResource {\n /** Elation EHR proxy */\n readonly elation: EhrProviderProxy;\n\n /** Hint Health proxy */\n readonly hint: EhrProviderProxy;\n\n constructor(apiBaseUrl: string, apiKey: string) {\n this.elation = new EhrProviderProxy(apiBaseUrl, \"elation\", apiKey);\n this.hint = new EhrProviderProxy(apiBaseUrl, \"hint\", apiKey);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Error\n// ---------------------------------------------------------------------------\n\nclass EhrProxyError extends Error {\n readonly provider: string;\n readonly method: string;\n readonly path: string;\n readonly status: number;\n\n constructor(provider: string, method: string, path: string, status: number) {\n super(\n `EHR proxy error: ${method} /api/ehr/${provider}${path} returned ${status}`,\n );\n this.name = \"EhrProxyError\";\n this.provider = provider;\n this.method = method;\n this.path = path;\n this.status = status;\n }\n}\n\nexport { EhrResource, EhrProviderProxy, EhrProxyError };\n","/**\n * NotesResource — push formatted notes to Elation.\n *\n * Caller assembles the note body (text + bullets). Truth owns the Elation\n * OAuth token and the HTTP call so applications can drop Elation\n * credentials from their own config.\n */\n\nexport interface NonVisitNoteBullet {\n text: string;\n category?: string;\n}\n\nexport interface PushNoteToElationInput {\n patient: number;\n physician: number;\n practice: number;\n note: { text: string; type?: string };\n document_date?: string;\n chart_date?: string;\n bullets?: NonVisitNoteBullet[];\n}\n\nexport interface PushNoteToElationResult {\n success: boolean;\n elationNoteId: number | null;\n}\n\nclass NotesError extends Error {\n readonly status: number;\n\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Notes ${operation} failed (HTTP ${status})`);\n this.name = \"NotesError\";\n this.status = status;\n }\n}\n\nexport interface PushConversationToElationInput {\n /** One-of: conversationId (Convex) or phonePair. */\n conversationId?: string;\n phonePair?: string;\n /** Actor user id — recorded on the audit row. */\n initiatedBy: string;\n /** Pre-assembled note body (transcript-style text). */\n noteText: string;\n bullets?: NonVisitNoteBullet[];\n notesPushed?: number;\n messagesPushed?: number;\n tasksPushed?: number;\n}\n\nexport interface PushConversationToElationResult {\n success: true;\n batchId: string;\n elationNoteId: number | null;\n notesPushed: number;\n messagesPushed: number;\n tasksPushed: number;\n}\n\nclass NotesResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(apiBaseUrl: string, apiKey: string) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n }\n\n /** 30s upstream timeout — Elation API has occasional slow hops; we\n * don't want a pending mutation to hold the UI thread indefinitely. */\n private static readonly REQUEST_TIMEOUT_MS = 30_000;\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const controller = new AbortController();\n const timeout = setTimeout(\n () => controller.abort(),\n NotesResource.REQUEST_TIMEOUT_MS,\n );\n\n let res: Response;\n try {\n res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n // Transport failures (DNS, TCP, TLS, AbortError) become typed\n // NotesError so consumers don't have to special-case raw fetch\n // exceptions. status=0 signals \"never made it to the server\".\n const isAbort =\n err instanceof Error &&\n (err.name === \"AbortError\" || err.name === \"TimeoutError\");\n const message = isAbort\n ? `Notes ${path} timed out after ${NotesResource.REQUEST_TIMEOUT_MS}ms`\n : err instanceof Error\n ? err.message\n : \"Notes request failed before response\";\n throw new NotesError(path, 0, message);\n } finally {\n clearTimeout(timeout);\n }\n\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new NotesError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n /**\n * Low-level — caller has already resolved Elation patient / physician\n * / practice. Use `pushConversationToElation` if you only have a\n * conversation handle.\n */\n async pushToElation(\n input: PushNoteToElationInput,\n ): Promise<PushNoteToElationResult> {\n return await this.post<PushNoteToElationResult>(\n \"/notes/push-to-elation\",\n input,\n );\n }\n\n /**\n * Orchestrator — pass a conversation handle + pre-assembled note text.\n * Truth resolves the linked patient via Convex, looks up\n * physician/practice in Elation, posts the note, and writes an audit\n * row to `elationSyncEvents`. Replaces CommHub's `pushNotesToElation`\n * Hasura action.\n */\n async pushConversationToElation(\n input: PushConversationToElationInput,\n ): Promise<PushConversationToElationResult> {\n return await this.post<PushConversationToElationResult>(\n \"/notes/push-conversation-to-elation\",\n input,\n );\n }\n}\n\nexport { NotesResource, NotesError };\n","/**\n * NotificationsResource — wraps Truth's /api/notifications/* endpoints.\n *\n * Server-side use (for example from a CommHub backend job or\n * another service): `truth.notifications.send({ userId, title, body })`.\n *\n * Client-side React usage lives in `@hipnation-truth/sdk/react` via\n * the `useNotifications` hook which mirrors `expo-notifications`.\n */\n\nexport type NotificationPlatform = \"ios\" | \"android\" | \"web\";\n\nexport interface RegisterDeviceInput {\n userId: string;\n platform: NotificationPlatform;\n nativeToken?: string;\n webPushSubscription?: {\n endpoint: string;\n keys: { p256dh: string; auth: string };\n };\n appVersion?: string;\n osVersion?: string;\n locale?: string;\n timezone?: string;\n /**\n * iOS only — which APNs endpoint the `nativeToken` was issued\n * against. The token bytes don't carry this; it's determined by\n * the build's `aps-environment` entitlement\n * (`development` ⇒ sandbox, `production` ⇒ production). When the\n * consumer is an Expo app, detect via\n * `Application.getIosPushNotificationServiceEnvironmentAsync()`\n * from `expo-application` and pass the normalised value here.\n * Omitting this falls back to the application's default\n * `pushConfig.ios.environment` server-side.\n */\n apnsEnvironment?: \"sandbox\" | \"production\";\n}\n\nexport interface RegisterDeviceResult {\n deviceId: string;\n action: \"inserted\" | \"updated\";\n snsEndpointArn?: string;\n}\n\nexport interface UnregisterDeviceInput {\n nativeToken?: string;\n deviceId?: string;\n}\n\nexport interface SendNotificationInput {\n userId: string;\n title: string;\n body: string;\n data?: Record<string, unknown>;\n badge?: number;\n sound?: string;\n}\n\nexport interface SendNotificationResult {\n delivered: number;\n failed?: number;\n suppressed?: boolean;\n suppressionReason?: string;\n}\n\nexport interface NotificationPreferences {\n channels: { sms: boolean; push: boolean; email: boolean; inApp: boolean };\n quietHours?: {\n enabled: boolean;\n start: string;\n end: string;\n timezone: string;\n };\n doNotDisturbUntil?: string;\n updatedAt: string;\n}\n\nexport interface UpdatePreferencesInput {\n userId: string;\n channels?: NotificationPreferences[\"channels\"];\n quietHours?: NotificationPreferences[\"quietHours\"];\n doNotDisturbUntil?: string | null;\n}\n\nexport interface ScheduleNotificationInput extends SendNotificationInput {\n /** ISO 8601 timestamp; must be strictly in the future. */\n scheduledAt: string;\n}\n\nexport interface ScheduleNotificationResult {\n jobId: string;\n scheduledAt: string;\n}\n\nexport interface CancelScheduledNotificationResult {\n cancelled: boolean;\n reason?: string;\n status?: string;\n}\n\nexport type ScheduledJobStatus =\n | \"pending\"\n | \"executed\"\n | \"cancelled\"\n | \"failed\";\n\nexport interface ScheduledNotification {\n jobId: string;\n userId: string;\n title: string;\n body: string;\n data?: unknown;\n badge?: number;\n sound?: string;\n scheduledAt: string;\n status: ScheduledJobStatus;\n resultHistoryId?: string;\n errorMessage?: string;\n createdAt: string;\n}\n\nexport interface PushEventPayload {\n title: string;\n body: string;\n data?: unknown;\n}\n\nclass NotificationsError extends Error {\n readonly status: number;\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Notifications ${operation} failed (HTTP ${status})`);\n this.name = \"NotificationsError\";\n this.status = status;\n }\n}\n\nclass NotificationsResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(apiBaseUrl: string, apiKey: string) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new NotificationsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n private async get<T>(\n path: string,\n params: Record<string, string>,\n ): Promise<T> {\n const url = new URL(`${this.baseUrl}/api${path}`);\n for (const [k, v] of Object.entries(params)) {\n url.searchParams.set(k, v);\n }\n const res = await fetch(url.toString(), {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new NotificationsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n private async delete<T>(path: string): Promise<T> {\n const res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"DELETE\",\n headers: {\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new NotificationsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n /**\n * Register a device (or refresh its metadata) for push delivery.\n * Safe to call repeatedly — the server dedupes by native token.\n */\n async registerDevice(\n input: RegisterDeviceInput,\n ): Promise<RegisterDeviceResult> {\n return this.post(\"/notifications/devices/register\", input);\n }\n\n /** Revoke a device — on sign-out or when the OS reports an invalid token. */\n async unregisterDevice(\n input: UnregisterDeviceInput,\n ): Promise<{ revoked: boolean }> {\n return this.post(\"/notifications/devices/unregister\", input);\n }\n\n /**\n * Send a push notification to every active device belonging to\n * `userId`. Honors the user's notificationPreferences (quiet hours,\n * DND, channel off) before publishing.\n */\n async send(input: SendNotificationInput): Promise<SendNotificationResult> {\n return this.post(\"/notifications/send\", input);\n }\n\n /** Read a user's notification preferences. Returns defaults when no row exists. */\n async getPreferences(userId: string): Promise<NotificationPreferences> {\n return this.get(\"/notifications/preferences\", { userId });\n }\n\n async updatePreferences(\n input: UpdatePreferencesInput,\n ): Promise<{ ok: boolean }> {\n return this.post(\"/notifications/preferences\", input);\n }\n\n /**\n * Schedule a future push notification. Convex's native scheduler\n * fires the send at `scheduledAt` and runs the same delivery\n * pipeline as `send()` (preferences, devices, history audit).\n *\n * Throws `NotificationsError` with status 400 if `scheduledAt` is\n * not strictly in the future.\n */\n async schedule(\n input: ScheduleNotificationInput,\n ): Promise<ScheduleNotificationResult> {\n return this.post(\"/notifications/schedule\", input);\n }\n\n /**\n * Cancel a pending scheduled notification. Returns `cancelled: false`\n * (no error) if the job has already executed, was previously\n * cancelled, or no longer exists — `reason` describes which case.\n */\n async cancelScheduled(\n jobId: string,\n ): Promise<CancelScheduledNotificationResult> {\n return this.delete(`/notifications/schedule/${encodeURIComponent(jobId)}`);\n }\n\n /**\n * List scheduled notifications for a user — pending, executed,\n * cancelled, or failed. Most-recent first. Default limit 100.\n */\n async listScheduled(\n userId: string,\n options?: { limit?: number },\n ): Promise<ScheduledNotification[]> {\n const params: Record<string, string> = { userId };\n if (options?.limit !== undefined) {\n params.limit = String(options.limit);\n }\n return this.get(\"/notifications/schedule\", params);\n }\n\n async getVapidKey(): Promise<string | null> {\n try {\n const result = await this.get<{ vapidPublicKey: string | null }>(\n \"/notifications/vapid-key\",\n {},\n );\n return result.vapidPublicKey;\n } catch {\n return null;\n }\n }\n\n onPushReceived(callback: (payload: PushEventPayload) => void): () => void {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return () => {};\n }\n const handler = (event: MessageEvent) => {\n if (event.data?.type === \"TRUTH_PUSH_RECEIVED\") {\n callback(event.data.payload);\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n return () =>\n navigator.serviceWorker.removeEventListener(\"message\", handler);\n }\n\n onPushTapped(callback: (payload: PushEventPayload) => void): () => void {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return () => {};\n }\n const handler = (event: MessageEvent) => {\n if (event.data?.type === \"TRUTH_PUSH_TAPPED\") {\n callback(event.data.payload);\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n return () =>\n navigator.serviceWorker.removeEventListener(\"message\", handler);\n }\n}\n\nexport { NotificationsResource, NotificationsError };\n","/**\n * PatientDetailsResource — merged Hint + Elation patient lookups.\n *\n * Backed by the Truth API at /api/patients/details/*, authenticated with\n * X-API-Key. Replaces CommHub's getPatientDetails / getPatientBasicDetails\n * / getPatientMedicalDetails actions.\n */\n\nexport interface PatientDetailsInput {\n hintId?: string;\n elationId?: string;\n}\n\nexport interface PatientDetailsResult {\n hint: unknown | null;\n elation: unknown | null;\n resolvedElationId: string | null;\n}\n\nexport interface PatientBasicDetailsResult {\n hint: unknown | null;\n elationId: string | null;\n}\n\nexport interface PatientMedicalDetailsResult {\n problems: unknown | null;\n medications: unknown | null;\n allergies: unknown | null;\n appointments: unknown | null;\n}\n\nclass PatientDetailsError extends Error {\n readonly status: number;\n\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Patient ${operation} failed (HTTP ${status})`);\n this.name = \"PatientDetailsError\";\n this.status = status;\n }\n}\n\nclass PatientDetailsResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(apiBaseUrl: string, apiKey: string) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new PatientDetailsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n async get(input: PatientDetailsInput): Promise<PatientDetailsResult> {\n return await this.post<PatientDetailsResult>(\"/patients/details\", input);\n }\n\n async getBasic(\n input: PatientDetailsInput,\n ): Promise<PatientBasicDetailsResult> {\n return await this.post<PatientBasicDetailsResult>(\n \"/patients/details/basic\",\n input,\n );\n }\n\n async getMedical(elationId: string): Promise<PatientMedicalDetailsResult> {\n return await this.post<PatientMedicalDetailsResult>(\n \"/patients/details/medical\",\n { elationId },\n );\n }\n\n /**\n * Trigger a server-side refresh of the patient's Elation medical\n * records (medications, problems, allergies, appointments) into the\n * Convex cache. Fire-and-forget — the UI should read via the Convex-\n * reactive `usePatientMedical` hook.\n */\n async refreshMedical(elationId: number): Promise<{\n totals: {\n medications: number;\n problems: number;\n allergies: number;\n appointments: number;\n };\n }> {\n return await this.post<{\n totals: {\n medications: number;\n problems: number;\n allergies: number;\n appointments: number;\n };\n }>(\"/patients/medical/refresh\", { elationId });\n }\n}\n\nexport { PatientDetailsResource, PatientDetailsError };\n","/**\n * PatientResource provides data access to normalized patient records\n * backed by Convex.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type { PaginatedResult } from \"../types/config\";\nimport type { Patient, PatientListOptions } from \"../types/patient\";\n\nclass PatientResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convexClient: ConvexHttpClient) {\n this.convex = convexClient;\n }\n\n /**\n * Get a patient by their Truth platform ID.\n */\n async get(id: string): Promise<Patient | null> {\n try {\n const result = await this.convex.query(\n \"patients:getById\" as never,\n {\n id,\n } as never,\n );\n return (result as Patient) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * Get a patient by their Elation EHR ID.\n */\n async getByElationId(elationId: string): Promise<Patient | null> {\n try {\n const result = await this.convex.query(\n \"patients:getByElationId\" as never,\n { elationId } as never,\n );\n return (result as Patient) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * Get a patient by their Hint EHR ID.\n */\n async getByHintId(hintId: string): Promise<Patient | null> {\n try {\n const result = await this.convex.query(\n \"patients:getByHintId\" as never,\n {\n hintId,\n } as never,\n );\n return (result as Patient) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * List patients with optional search, pagination, and limit.\n */\n async list(options?: PatientListOptions): Promise<PaginatedResult<Patient>> {\n try {\n const result = await this.convex.query(\n \"patients:list\" as never,\n {\n search: options?.search,\n limit: options?.limit,\n cursor: options?.cursor,\n } as never,\n );\n\n const typed = result as PaginatedResult<Patient> | undefined;\n\n return typed ?? { data: [], cursor: null, hasMore: false };\n } catch {\n return { data: [], cursor: null, hasMore: false };\n }\n }\n}\n\nexport { PatientResource };\n","/**\n * PhysiciansResource — Convex-backed physician lookups.\n *\n * Populated from Elation by Truth's daily PhysiciansBackfillCron. Replaces\n * per-physician Elation HTTP hops with a single Convex batch query.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\n\nexport interface 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\nclass PhysiciansResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convex: ConvexHttpClient) {\n this.convex = convex;\n }\n\n /**\n * Resolve a batch of physicians by Elation IDs. Missing ids are dropped.\n */\n async getByElationIds(ids: number[]): Promise<Physician[]> {\n if (ids.length === 0) {\n return [];\n }\n try {\n const rows = (await this.convex.query(\n \"physicians:getByElationIds\" as never,\n { ids } as never,\n )) as Physician[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n\n async getByElationId(id: number): Promise<Physician | null> {\n try {\n const row = (await this.convex.query(\n \"physicians:getByElationId\" as never,\n { id } as never,\n )) as Physician | null;\n return row ?? null;\n } catch {\n return null;\n }\n }\n\n async listByPractice(practice: number, limit?: number): Promise<Physician[]> {\n try {\n const rows = (await this.convex.query(\n \"physicians:listByPractice\" as never,\n { practice, limit } as never,\n )) as Physician[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n}\n\nexport { PhysiciansResource };\n","/**\n * RemindersResource — schedule, cancel, and list conversation reminders.\n *\n * Backed by Convex mutations/queries (durable scheduled functions) — replaces\n * the in-memory setTimeout scheduler that used to live in CommHub's NestJS\n * backend.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type {\n Reminder,\n ScheduleReminderInput,\n ScheduleReminderResult,\n} from \"../types/reminder\";\n\nclass RemindersResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convexClient: ConvexHttpClient) {\n this.convex = convexClient;\n }\n\n /**\n * Schedule a reminder to fire at `remindAt`. Returns the reminder id,\n * which callers should store if they may want to cancel it later.\n */\n async schedule(\n input: ScheduleReminderInput,\n ): Promise<ScheduleReminderResult> {\n const remindAt =\n input.remindAt instanceof Date\n ? input.remindAt.toISOString()\n : input.remindAt;\n\n const result = (await this.convex.mutation(\n \"reminders:schedule\" as never,\n {\n conversationId: input.conversationId,\n remindAt,\n note: input.note,\n createdBy: input.createdBy,\n } as never,\n )) as ScheduleReminderResult;\n\n return result;\n }\n\n /**\n * Cancel a pending reminder. No-op if the reminder has already fired or\n * been cancelled.\n */\n async cancel(\n reminderId: string,\n cancelledBy: string,\n ): Promise<{ reminderId: string; status: string }> {\n return (await this.convex.mutation(\n \"reminders:cancel\" as never,\n { reminderId, cancelledBy } as never,\n )) as { reminderId: string; status: string };\n }\n\n /**\n * List reminders for a conversation (most recent first).\n */\n async listByConversation(conversationId: string): Promise<Reminder[]> {\n try {\n const rows = (await this.convex.query(\n \"reminders:listByConversation\" as never,\n { conversationId } as never,\n )) as Reminder[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n}\n\nexport { RemindersResource };\n","/**\n * TasksResource — create, update status, list, get tasks.\n *\n * Backed by Convex. Replaces CommHub's createTaskWithNotification +\n * updateTaskStatusWithNotification actions. Push notification side-effect\n * is emitted downstream via Kinesis.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type {\n ConversationTaskMarkSeenInput,\n CreateTaskInput,\n Task,\n TaskStatus,\n UpdateTaskStatusInput,\n} from \"../types/task\";\n\nclass TasksResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convexClient: ConvexHttpClient) {\n this.convex = convexClient;\n }\n\n async create(\n input: CreateTaskInput,\n ): Promise<{ taskId: string; status: TaskStatus }> {\n return (await this.convex.mutation(\n \"tasks:create\" as never,\n input as never,\n )) as { taskId: string; status: TaskStatus };\n }\n\n async updateStatus(input: UpdateTaskStatusInput): Promise<{\n taskId: string;\n status: TaskStatus;\n previousStatus: TaskStatus;\n }> {\n return (await this.convex.mutation(\n \"tasks:updateStatus\" as never,\n input as never,\n )) as {\n taskId: string;\n status: TaskStatus;\n previousStatus: TaskStatus;\n };\n }\n\n async get(taskId: string): Promise<Task | null> {\n try {\n const row = (await this.convex.query(\n \"tasks:get\" as never,\n { taskId } as never,\n )) as Task | null;\n return row ?? null;\n } catch {\n return null;\n }\n }\n\n async listByAssignee(\n assignedTo: string,\n options?: { status?: TaskStatus; limit?: number },\n ): Promise<Task[]> {\n try {\n const rows = (await this.convex.query(\n \"tasks:listByAssignee\" as never,\n {\n assignedTo,\n status: options?.status,\n limit: options?.limit,\n } as never,\n )) as Task[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n\n async listOpen(limit?: number): Promise<Task[]> {\n try {\n const rows = (await this.convex.query(\n \"tasks:listOpen\" as never,\n { limit } as never,\n )) as Task[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n\n /**\n * Mark a conversation task as seen by the given user. Appends `userId`\n * to the `seenBy` array on the `conversationTasks` row if not already\n * present. Replaces CommHub's `useMark_Event_Activity_SeenMutation`.\n *\n * @returns `{ ok: true }` on success.\n * @throws if the task does not exist in Convex.\n */\n async markSeen(input: ConversationTaskMarkSeenInput): Promise<{ ok: true }> {\n return (await this.convex.mutation(\n \"conversationTasks:markSeen\" as never,\n input as never,\n )) as { ok: true };\n }\n}\n\nexport { TasksResource };\n","/**\n * TranslationResource — Azure Translator proxy through Truth API.\n *\n * All three operations are HTTP calls to Truth's oRPC endpoints at\n * `/api/translation/*`, authenticated with the same X-API-Key as the\n * event tracker.\n */\n\nimport type {\n DetectionResult,\n TranslateBatchInput,\n TranslateTextInput,\n TranslationResult,\n} from \"../types/translation\";\n\nclass TranslationError extends Error {\n readonly status: number;\n readonly operation: string;\n\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Translation ${operation} failed (HTTP ${status})`);\n this.name = \"TranslationError\";\n this.status = status;\n this.operation = operation;\n }\n}\n\nclass TranslationResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(apiBaseUrl: string, apiKey: string) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const url = `${this.baseUrl}/api${path}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": this.apiKey,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new TranslationError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n async translate(input: TranslateTextInput): Promise<TranslationResult> {\n return await this.post<TranslationResult>(\"/translation/translate\", input);\n }\n\n async translateBatch(\n input: TranslateBatchInput,\n ): Promise<TranslationResult[]> {\n const response = await this.post<{ results: TranslationResult[] }>(\n \"/translation/translate-batch\",\n input,\n );\n return response.results;\n }\n\n async detect(text: string): Promise<DetectionResult> {\n return await this.post<DetectionResult>(\"/translation/detect\", { text });\n }\n}\n\nexport { TranslationResource, TranslationError };\n","/**\n * UserSettingsResource — typed client for the Truth `userSettings` Convex\n * table. Exposes the one mutation CommHub's Settings screen needs:\n * `updateNotifications({ userId, notificationsEnabled })`.\n *\n * Server-side / imperative use:\n * truth.userSettings.updateNotifications({ userId, notificationsEnabled: true })\n *\n * Reactive React use lives in `@hipnation-truth/sdk/react` via\n * `useUserSettings`.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport { makeFunctionReference } from \"convex/server\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UserSettings {\n _id: string;\n userId: string;\n notificationsEnabled: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface UpdateNotificationsInput {\n userId: string;\n notificationsEnabled: boolean;\n}\n\nexport interface UpdateNotificationsResult {\n action: \"inserted\" | \"updated\";\n id: string;\n}\n\n// ---------------------------------------------------------------------------\n// Convex function references (string-based, SDK stays decoupled from codegen)\n// ---------------------------------------------------------------------------\n\nconst upsertNotificationsRef = makeFunctionReference<\n \"mutation\",\n { userId: string; notificationsEnabled: boolean },\n UpdateNotificationsResult\n>(\"userSettings:upsertNotifications\");\n\n// ---------------------------------------------------------------------------\n// Resource\n// ---------------------------------------------------------------------------\n\nclass UserSettingsResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convex: ConvexHttpClient) {\n this.convex = convex;\n }\n\n /**\n * Upsert notification preferences for a user. Creates the row if it\n * doesn't exist; patches `notificationsEnabled` + `updatedAt` otherwise.\n */\n async updateNotifications(\n input: UpdateNotificationsInput,\n ): Promise<UpdateNotificationsResult> {\n return this.convex.mutation(upsertNotificationsRef, input);\n }\n}\n\nexport { UserSettingsResource };\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 * 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 * TruthClient -- main entry point for the @hipnation-truth/sdk package.\n *\n * Provides:\n * - `.patients` Resource-based patient data access (Convex-backed)\n * - `.appointments` Resource-based appointment data access (Convex-backed)\n * - `.ehr` EHR proxy access (Elation, Hint)\n * - `.messages` Messaging proxy access (Dialpad)\n * - `.track()` Fire-and-forget event tracking (batched HTTP -> Truth API)\n * - `.identify()` Set default actor context for subsequent events\n * - `.flush()` Force flush of buffered events (for graceful shutdown)\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport { AuthAwareConvexHttpClient } from \"./auth-convex-client\";\nimport { AppointmentResource } from \"./resources/appointments\";\nimport { AttachmentsResource } from \"./resources/attachments\";\nimport { ConversationsResource } from \"./resources/conversations\";\nimport { MessagesResource } from \"./resources/dialpad\";\nimport { EhrResource } from \"./resources/ehr\";\nimport { NotesResource } from \"./resources/notes\";\nimport { NotificationsResource } from \"./resources/notifications\";\nimport { PatientDetailsResource } from \"./resources/patient-details\";\nimport { PatientResource } from \"./resources/patients\";\nimport { PhysiciansResource } from \"./resources/physicians\";\nimport { RemindersResource } from \"./resources/reminders\";\nimport { TasksResource } from \"./resources/tasks\";\nimport { TranslationResource } from \"./resources/translation\";\nimport { UserSettingsResource } from \"./resources/user-settings\";\nimport type {\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"./tracking/events\";\nimport {\n DEFAULT_BATCH_SIZE,\n DEFAULT_FLUSH_INTERVAL_MS,\n Tracker,\n} from \"./tracking/tracker\";\nimport type { ActorContext, TruthClientConfig } from \"./types/config\";\nimport {\n isWebPushSupported,\n registerServiceWorker,\n subscribeToPush,\n subscriptionToJSON,\n} from \"./web-push\";\n\n// ---------------------------------------------------------------------------\n// Environment -> Convex URL mapping\n// ---------------------------------------------------------------------------\n\n// Environment → Convex deployment URL.\n//\n// Topology mirrors API_URLS in tracker.ts:\n// local / staging / stg / sandbox → sandbox Convex (courteous-duck-623)\n// uat / production → production Convex (gallant-gecko-217)\n//\n// UAT shares production resources. Staging is the isolated sandbox env.\nconst 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// TruthClient\n// ---------------------------------------------------------------------------\n\nclass TruthClient {\n /** Patient data resource (Convex-backed) */\n readonly patients: PatientResource;\n\n /** Appointment data resource (Convex-backed) */\n readonly appointments: AppointmentResource;\n\n /** EHR proxy — typed access to Elation and Hint APIs through Truth */\n readonly ehr: EhrResource;\n\n /** Messaging proxy — typed access to Dialpad APIs through Truth */\n readonly messages: MessagesResource;\n\n /** Conversation reminders (Convex-backed, durable scheduler) */\n readonly reminders: RemindersResource;\n\n /** Translation (Azure Translator proxy) */\n readonly translation: TranslationResource;\n\n /** Tasks (Convex-backed) */\n readonly tasks: TasksResource;\n\n /** Patient details — merged Hint + Elation lookups via Truth API */\n readonly patientDetails: PatientDetailsResource;\n\n /** Attachments — presigned S3 upload/download + Convex metadata */\n readonly attachments: AttachmentsResource;\n\n /** Notes — push formatted notes to Elation */\n readonly notes: NotesResource;\n\n /** Physicians (Convex-backed cache from Elation) */\n readonly physicians: PhysiciansResource;\n\n /** Push / web notifications (AWS End User Messaging) */\n readonly notifications: NotificationsResource;\n\n /** Conversation-tied writes — notes, tasks, outbound messages. */\n readonly conversations: ConversationsResource;\n\n /** User settings — notification preferences (Convex-backed) */\n readonly userSettings: UserSettingsResource;\n\n private readonly convex: ConvexHttpClient;\n private readonly tracker: Tracker;\n private _vapidPublicKey: string | null = null;\n private _webPushReady: Promise<void> | null = null;\n private readonly _serviceWorkerPath: string;\n\n constructor(config: TruthClientConfig) {\n // Resolve Convex URL\n const convexUrl =\n config.convexUrl ?? CONVEX_URLS[config.environment] ?? CONVEX_URLS.local;\n\n // Initialize Convex HTTP client for data access. When the host app\n // injects `getAuthToken`, every call carries the user's Clerk JWT\n // (refreshed per call — see AuthAwareConvexHttpClient).\n this.convex = new AuthAwareConvexHttpClient(convexUrl, config.getAuthToken);\n\n // Initialize event tracker\n this.tracker = new Tracker({\n apiKey: config.apiKey,\n environment: config.environment,\n source: config.source ?? \"unknown\",\n sourceVersion: config.sourceVersion ?? \"unknown\",\n tenantId: config.tenantId ?? \"\",\n batchSize: config.batchSize ?? DEFAULT_BATCH_SIZE,\n flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,\n apiBaseUrl: config.apiBaseUrl,\n });\n\n const apiUrl = this.tracker.apiUrl;\n\n // Initialize resources\n this.patients = new PatientResource(this.convex);\n this.appointments = new AppointmentResource(this.convex);\n this.ehr = new EhrResource(apiUrl, config.apiKey);\n this.messages = new MessagesResource(apiUrl, config.apiKey);\n this.reminders = new RemindersResource(this.convex);\n this.translation = new TranslationResource(apiUrl, config.apiKey);\n this.tasks = new TasksResource(this.convex);\n this.patientDetails = new PatientDetailsResource(apiUrl, config.apiKey);\n this.attachments = new AttachmentsResource(\n apiUrl,\n config.apiKey,\n this.convex,\n );\n this.notes = new NotesResource(apiUrl, config.apiKey);\n this.physicians = new PhysiciansResource(this.convex);\n this.notifications = new NotificationsResource(apiUrl, config.apiKey);\n this.conversations = new ConversationsResource(\n apiUrl,\n config.apiKey,\n this.convex,\n );\n this.userSettings = new UserSettingsResource(this.convex);\n this._serviceWorkerPath = config.serviceWorkerPath ?? \"/truth-sw.js\";\n\n if (\n typeof window !== \"undefined\" &&\n isWebPushSupported() &&\n config.autoInitServiceWorker !== false\n ) {\n this._webPushReady = this.initWebPush();\n }\n }\n\n get vapidPublicKey(): string | null {\n return this._vapidPublicKey;\n }\n\n get webPushReady(): Promise<void> | null {\n return this._webPushReady;\n }\n\n private async initWebPush(): Promise<void> {\n try {\n const key = await this.notifications.getVapidKey();\n if (!key) {\n return;\n }\n this._vapidPublicKey = key;\n\n const registration = await registerServiceWorker(this._serviceWorkerPath);\n await navigator.serviceWorker.ready;\n const subscription = await subscribeToPush(registration, key);\n const subJSON = subscriptionToJSON(subscription);\n\n await this.notifications.registerDevice({\n userId: \"__pending__\",\n platform: \"web\",\n webPushSubscription: subJSON,\n locale: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n });\n } catch {\n // Web push init is best-effort — don't break the client.\n }\n }\n\n /**\n * The resolved Truth API base URL for this environment.\n * Use this when making HTTP calls to Truth's proxy endpoints\n * (e.g., EHR proxy, messages proxy).\n *\n * @example\n * ```ts\n * const url = `${truth.apiBaseUrl}/api/ehr/elation/patients/123`;\n * ```\n */\n get apiBaseUrl(): string {\n return this.tracker.apiUrl;\n }\n\n /**\n * Track an event. Fire-and-forget -- the event is buffered internally\n * and flushed in batches to the Truth API.\n *\n * @example\n * ```ts\n * truth.track('conversation.message_sent.v1', {\n * channel: 'sms',\n * direction: 'outbound',\n * message_chars: 140,\n * has_attachment: false,\n * provider_system: 'dialpad',\n * });\n * ```\n */\n track<T extends EventType>(\n eventType: T,\n payload: EventPayloadMap[T],\n options?: TrackOptions,\n ): void {\n this.tracker.track(eventType, payload, options);\n }\n\n /**\n * Set the default actor context for all subsequent tracked events.\n * Can be overridden per-event via TrackOptions.\n *\n * @example\n * ```ts\n * truth.identify('user_123', 'user');\n * ```\n */\n identify(actorId: string, actorType: ActorContext[\"actorType\"]): void {\n this.tracker.setActor({ actorId, actorType });\n }\n\n /**\n * Flush all buffered events immediately. Returns a Promise that resolves\n * when the flush completes. Use this for graceful shutdown.\n *\n * @example\n * ```ts\n * process.on('SIGTERM', async () => {\n * await truth.flush();\n * process.exit(0);\n * });\n * ```\n */\n async flush(): Promise<void> {\n await this.tracker.flush();\n }\n\n /**\n * Gracefully shut down the client. Flushes all pending events and\n * releases resources.\n */\n async destroy(): Promise<void> {\n await this.tracker.shutdown();\n }\n}\n\nexport { TruthClient };\n","/**\n * Versioned cache envelope + bounded eviction.\n *\n * Two concerns live here:\n *\n * 1. **Versioning.** Every cached value is wrapped in `{ v, savedAt,\n * data }`. {@link SCHEMA_VERSION} is bumped whenever the *shape* of\n * any cached query changes; readers drop entries whose `v` doesn't\n * match, so a stale on-disk mirror from an older app build can never\n * hydrate the UI with a shape the new code doesn't expect. Under\n * Path A this same constant is handed to TanStack's\n * `PersistQueryClientProvider` as the `buster`, which discards the\n * whole dehydrated cache on a version bump.\n *\n * 2. **Eviction.** A device mirror must stay bounded. {@link\n * putWithEviction} maintains an index of `key → savedAt` and evicts\n * the oldest entries once the entry count exceeds the cap.\n *\n * **Clock injection:** the pure helpers never call `Date.now()` — the\n * caller passes `now` so the eviction order and envelope timestamps are\n * deterministic under test. The provider supplies `Date.now()` at the\n * real call site.\n */\n\nimport type { OfflineStore } from \"./store\";\n\n/**\n * Bump on ANY change to a cached query's shape → invalidates old entries.\n *\n * **When to bump:** whenever the return shape of an allowlisted query\n * changes (field added/removed/renamed/retyped), or the envelope/cache\n * format here changes. **Procedure:** increment this integer by 1 in one\n * commit. That changes every `makeCacheKey` (Path B) and the TanStack\n * persist `buster` (Path A, via `provider.ts`), so on the next launch the\n * old mirror is ignored and refilled from live Convex — no migration, no\n * manual wipe. Never reuse a previous number.\n */\nexport const SCHEMA_VERSION = 1;\n\n/** Default cap on the number of cached data entries (excludes the index). */\nexport const DEFAULT_MAX_ENTRIES = 200;\n\n/** Reserved key holding the `{ key: savedAt }` eviction index. */\nexport const INDEX_KEY = \"__truth_offline_index__\";\n\nexport interface Envelope<T> {\n /** Schema version this entry was written under. */\n v: number;\n /** Epoch millis the entry was written (injected clock). */\n savedAt: number;\n /** The cached payload. */\n data: T;\n}\n\n/** Serialize `data` into a versioned envelope string. `now` is injected. */\nexport function writeEnvelope<T>(data: T, now: number): string {\n const envelope: Envelope<T> = { v: SCHEMA_VERSION, savedAt: now, data };\n return JSON.stringify(envelope);\n}\n\n/**\n * Parse a stored envelope. Returns `undefined` when the value is\n * missing, malformed, or written under a different `SCHEMA_VERSION`\n * (the version-mismatch drop). Never throws.\n */\nexport function readEnvelope<T>(\n raw: string | null | undefined,\n): { data: T; savedAt: number } | undefined {\n if (raw == null) {\n return undefined;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (typeof parsed !== \"object\" || parsed === null) {\n return undefined;\n }\n // Validate the envelope shape before trusting it: a malformed entry must\n // fail closed (drop to `undefined`) rather than hand back a non-number\n // `savedAt` or a missing `data` that breaks consumers downstream.\n const envelope = parsed as Partial<Envelope<T>>;\n if (\n envelope.v !== SCHEMA_VERSION ||\n typeof envelope.savedAt !== \"number\" ||\n !Number.isFinite(envelope.savedAt) ||\n !(\"data\" in envelope)\n ) {\n return undefined;\n }\n return { data: envelope.data as T, savedAt: envelope.savedAt };\n}\n\ntype EvictionIndex = Record<string, number>;\n\nfunction readIndex(store: OfflineStore): EvictionIndex {\n const raw = store.get(INDEX_KEY);\n if (raw == null) {\n return {};\n }\n try {\n const parsed = JSON.parse(raw);\n return typeof parsed === \"object\" && parsed !== null\n ? (parsed as EvictionIndex)\n : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Write `data` under `key` as a versioned envelope and record it in the\n * eviction index. When the index exceeds `maxEntries`, evict the oldest\n * entries (lowest `savedAt`) — never the entry just written — deleting\n * both their data and their index slot.\n */\nexport function putWithEviction<T>(\n store: OfflineStore,\n key: string,\n data: T,\n now: number,\n maxEntries: number = DEFAULT_MAX_ENTRIES,\n): void {\n store.set(key, writeEnvelope(data, now));\n\n const index = readIndex(store);\n index[key] = now;\n\n const keys = Object.keys(index);\n if (keys.length > maxEntries) {\n const overflow = keys.length - maxEntries;\n // Oldest first, never the just-written key. Excluding `key` BEFORE\n // slicing (rather than skipping it mid-loop) guarantees exactly\n // `overflow` deletions even when `now` is non-monotonic and the new\n // key sorts among the oldest — otherwise a skipped victim would\n // leave the cache one entry over the cap.\n const victims = keys\n .filter((k) => k !== key)\n .sort((a, b) => index[a] - index[b])\n .slice(0, overflow);\n for (const victim of victims) {\n store.delete(victim);\n delete index[victim];\n }\n }\n\n store.set(INDEX_KEY, JSON.stringify(index));\n}\n","/**\n * TanStack Query persister backed by the injected {@link OfflineStore}.\n *\n * Under Path A the whole dehydrated query cache is serialized as a\n * single blob and written through the synchronous `OfflineStore` (MMKV\n * on `ch/`). On cold start `PersistQueryClientProvider` calls\n * `restoreClient()` to rehydrate that blob before the Convex websocket\n * reconnects, which is what makes last-known data render with no\n * network.\n *\n * The store is synchronous, so all three methods resolve immediately —\n * but the `Persister` contract allows returning a value or a promise, so\n * returning the raw values is fine.\n */\n\nimport type {\n PersistedClient,\n Persister,\n} from \"@tanstack/react-query-persist-client\";\nimport type { OfflineStore } from \"./store\";\n\n/** Single key the dehydrated TanStack cache is stored under. */\nexport const PERSIST_CACHE_KEY = \"truth-offline:tanstack-cache\";\n\n/**\n * Minimal structural guard for a dehydrated TanStack client. We only\n * assert the fields restore actually relies on (`timestamp` number +\n * `clientState` object) — enough to reject a corrupt / foreign blob\n * before it reaches `PersistQueryClientProvider`, without coupling to\n * TanStack's internal `clientState` shape.\n */\nfunction isPersistedClient(value: unknown): value is PersistedClient {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Partial<PersistedClient>;\n return (\n typeof candidate.timestamp === \"number\" &&\n Number.isFinite(candidate.timestamp) &&\n typeof candidate.clientState === \"object\" &&\n candidate.clientState !== null\n );\n}\n\n/**\n * Create a synchronous `Persister` that reads/writes the dehydrated\n * TanStack cache through `store`. Restore tolerates absent or corrupt\n * data by returning `undefined` (TanStack then starts cold).\n */\nexport function createOfflinePersister(\n store: OfflineStore,\n cacheKey: string = PERSIST_CACHE_KEY,\n): Persister {\n return {\n persistClient(client: PersistedClient): void {\n try {\n store.set(cacheKey, JSON.stringify(client));\n } catch {\n // Best-effort mirror — a serialization failure must never crash\n // the host app. Worst case we lose offline reads this session.\n }\n },\n restoreClient(): PersistedClient | undefined {\n // `store.get` is inside the try too: an injected store can throw\n // (e.g. MMKV decryption failure), and a cold start must fail closed\n // rather than crash the host app.\n try {\n const raw = store.get(cacheKey);\n if (raw == null) {\n return undefined;\n }\n const parsed = JSON.parse(raw) as unknown;\n // Fail closed on a wrong shape, not just on parse errors: a blob\n // missing `timestamp`/`clientState` would otherwise hydrate and\n // then blow up later inside TanStack's restore in a far less\n // controlled spot. Validate the minimal contract here.\n if (!isPersistedClient(parsed)) {\n return undefined;\n }\n return parsed;\n } catch {\n return undefined;\n }\n },\n removeClient(): void {\n try {\n store.delete(cacheKey);\n } catch {\n // Best-effort cleanup; a store failure must never crash the\n // logout / cache-clear paths (same fail-closed stance as the\n // persist/restore methods above).\n }\n },\n };\n}\n\n/**\n * Read the epoch-millis timestamp the persisted cache was last written\n * (TanStack stamps `timestamp` on every persist → it tracks the most\n * recent successful sync). Returns `null` when nothing is persisted or\n * the blob is unreadable. Powers the \"Offline · updated {relative}\"\n * indicator without the app having to know the dehydrated cache format.\n */\nexport function readPersistedSavedAt(\n store: OfflineStore,\n cacheKey: string = PERSIST_CACHE_KEY,\n): number | null {\n // `store.get` inside the try as well — fail closed (null) if the\n // injected store throws, same as a parse failure.\n try {\n const raw = store.get(cacheKey);\n if (raw == null) {\n return null;\n }\n const parsed = JSON.parse(raw) as { timestamp?: number };\n return typeof parsed.timestamp === \"number\" ? parsed.timestamp : null;\n } catch {\n return null;\n }\n}\n","/**\n * Offline mirror storage seam.\n *\n * The Truth SDK is consumed by two apps with very different storage\n * stories:\n * - `ch/` (Expo / React Native) — has access to native, encrypted,\n * synchronous KV (MMKV) and wants durable offline reads.\n * - `truth/apps/web` (Next.js) — has no such store and must behave\n * exactly as it does today.\n *\n * So the SDK never imports a native module. Instead the consuming app\n * *injects* an `OfflineStore` implementation through `<TruthProvider>`.\n * When none is injected the SDK falls back to {@link NoopStore} and the\n * entire offline layer becomes a transparent pass-through.\n *\n * The interface is deliberately **synchronous**: cold-start hydration\n * has to read the last-known value during the first render, before any\n * effect or promise resolves, so async storage (AsyncStorage) is not\n * sufficient here. MMKV's `getString`/`set` are synchronous and map\n * onto this directly.\n */\n\n/** Synchronous KV mirror. Sync getters enable instant cold-start hydration. */\nexport interface OfflineStore {\n /** Return the stored string for `key`, or `null` when absent. */\n get(key: string): string | null;\n /** Write `value` under `key`, overwriting any prior value. */\n set(key: string, value: string): void;\n /** Remove a single `key`. No-op when absent. */\n delete(key: string): void;\n /**\n * Wipe every key this store owns. Called on logout / user switch so a\n * second user on the same device never sees the first user's cached\n * PHI. MUST clear durably (survive the next cold start).\n */\n clearAll(): void;\n}\n\n/**\n * Used when no store is injected (e.g. `truth/apps/web`) → the offline\n * layer is a no-op and behavior is identical to a build without it.\n */\nexport class NoopStore implements OfflineStore {\n get(): string | null {\n return null;\n }\n set(): void {}\n delete(): void {}\n clearAll(): void {}\n}\n\n/**\n * Shared singleton so every `<TruthProvider>` that omits `offlineStore`\n * resolves to the *same* Noop instance. Keeps context identity stable\n * (no needless re-renders) and lets `isNoopStore` cheaply detect the\n * \"no store injected\" case to skip persistence wiring.\n */\nexport const NOOP_STORE: OfflineStore = new NoopStore();\n\n/** True when `store` is the injected-nothing default (web / flag off). */\nexport function isNoopStore(store: OfflineStore | undefined | null): boolean {\n return store == null || store instanceof NoopStore;\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\";\nimport { useTruthSdkContext } from \"./provider\";\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 /**\n * Truth API base URL — e.g. `https://app.truth.communication-hub.com`.\n * Optional when the hook is mounted under `<TruthProvider>`: the\n * provider resolves the URL from the environment (production + uat →\n * prod Lambda; everything else → sandbox). Pass an override to point\n * at a per-PR Truth stage (e.g. `https://pr-132-app.sandbox...`).\n */\n apiBaseUrl?: string;\n /**\n * `hn_live_*` API key for the caller's application. Optional when\n * provided via `<TruthProvider apiKey={...}>` — the SDK reads it from\n * context so each hook call doesn't have to repeat the prop.\n */\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 // Fall back to TruthProvider context for apiBaseUrl + apiKey so\n // CommHub (and any other SDK consumer) doesn't have to thread the\n // REST endpoint creds through every hook call. Explicit options\n // still win for per-call overrides (e.g. PR previews).\n const sdkContext = useTruthSdkContext();\n const apiBaseUrl = options.apiBaseUrl ?? sdkContext?.apiBaseUrl ?? \"\";\n const apiKey = options.apiKey ?? sdkContext?.apiKey ?? \"\";\n\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 `${apiBaseUrl}/api/notifications/vapid-key`,\n {\n headers: {\n Accept: \"application/json\",\n \"X-API-Key\": 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 }, [apiBaseUrl, 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 `${apiBaseUrl}/api/notifications/devices/register`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": 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 // Fail fast instead of POSTing to a relative `/api/...` on the host\n // app when creds are unresolved (no <TruthProvider> / no options).\n if (!apiBaseUrl || !apiKey) {\n return { ok: false, reason: \"missing_truth_config\" };\n }\n\n const res = await fetch(\n `${apiBaseUrl}/api/notifications/devices/register`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": 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 apiBaseUrl,\n 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(`${apiBaseUrl}/api/notifications/devices/unregister`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify({ nativeToken: devicePushToken }),\n }).catch(() => {\n /* non-fatal */\n });\n setDevicePushToken(null);\n }, [apiBaseUrl, 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\n/**\n * useNotificationsActions — context-aware factory for the REST-backed\n * notification actions exposed by `NotificationsResource` (send,\n * schedule, get/update preferences). Reads `apiBaseUrl` + `apiKey`\n * from `<TruthProvider>` so call sites don't have to instantiate the\n * resource with creds inline.\n *\n * Returns memoized callbacks so they're stable across re-renders.\n */\nexport interface UseNotificationsActions {\n send: (input: {\n userId: string;\n title: string;\n body: string;\n data?: Record<string, unknown>;\n badge?: number;\n sound?: string;\n }) => Promise<{\n delivered: number;\n failed?: number;\n suppressed?: boolean;\n suppressionReason?: string;\n }>;\n schedule: (input: {\n userId: string;\n title: string;\n body: string;\n scheduledAt: string;\n data?: Record<string, unknown>;\n }) => Promise<{ jobId: string }>;\n getPreferences: (userId: string) => Promise<Record<string, unknown>>;\n updatePreferences: (\n userId: string,\n prefs: Record<string, unknown>,\n ) => Promise<Record<string, unknown>>;\n}\n\nexport function useNotificationsActions(): UseNotificationsActions {\n const sdkContext = useTruthSdkContext();\n const apiBaseUrl = sdkContext?.apiBaseUrl ?? \"\";\n const apiKey = sdkContext?.apiKey ?? \"\";\n\n const post = useCallback(\n async <T>(path: string, body: unknown): Promise<T> => {\n if (!apiBaseUrl || !apiKey) {\n throw new Error(\n \"useNotificationsActions: missing apiBaseUrl/apiKey — wrap in <TruthProvider> or pass options\",\n );\n }\n const res = await fetch(`${apiBaseUrl}/api${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `Truth API ${path} failed (${res.status}): ${text.slice(0, 200)}`,\n );\n }\n return (await res.json()) as T;\n },\n [apiBaseUrl, apiKey],\n );\n\n const get = useCallback(\n async <T>(path: string): Promise<T> => {\n const res = await fetch(`${apiBaseUrl}/api${path}`, {\n method: \"GET\",\n headers: { Accept: \"application/json\", \"X-API-Key\": apiKey },\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `Truth API ${path} failed (${res.status}): ${text.slice(0, 200)}`,\n );\n }\n return (await res.json()) as T;\n },\n [apiBaseUrl, apiKey],\n );\n\n const send = useCallback(\n (\n input: Parameters<UseNotificationsActions[\"send\"]>[0],\n ): ReturnType<UseNotificationsActions[\"send\"]> =>\n post(\"/notifications/send\", input),\n [post],\n );\n\n const schedule = useCallback(\n (\n input: Parameters<UseNotificationsActions[\"schedule\"]>[0],\n ): ReturnType<UseNotificationsActions[\"schedule\"]> =>\n post(\"/notifications/schedule\", input),\n [post],\n );\n\n const getPreferences = useCallback(\n (userId: string) =>\n get<Record<string, unknown>>(\n `/notifications/preferences/${encodeURIComponent(userId)}`,\n ),\n [get],\n );\n\n const updatePreferences = useCallback(\n (userId: string, prefs: Record<string, unknown>) =>\n post<Record<string, unknown>>(\n `/notifications/preferences/${encodeURIComponent(userId)}`,\n prefs,\n ),\n [post],\n );\n\n return { send, schedule, getPreferences, updatePreferences };\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 * 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 type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist: family members shown in the patient panel must\n// render from the mirror offline. Aliased so the call site below is unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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 = !!(\n input?.phoneNumbers && input.phoneNumbers.length > 0\n );\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 type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist: previously-run patient searches re-render from the\n// mirror offline. Aliased so the call site below is unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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 /** Middle name — surfaced so the picker renders the full legal name (B9). */\n middleName: string | undefined;\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 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// Offline-reads allowlist: inbox card names / office / EHR ids must render\n// from the mirror offline. Aliased so the call sites below are unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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) {\n return undefined;\n }\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) {\n return undefined;\n }\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 * 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 type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { Reminder } from \"../types/reminder\";\nimport type { UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist (Phase 2.1): the inbox reminder clock is part\n// of the durable mirror. Aliased so the call site below is unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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 hooks for conversation tasks (My Tasks surface).\n *\n * Provides a reactive `useMutation` wrapper around the\n * `conversationTasks:markSeen` Convex mutation so CommHub can replace\n * `useMark_Event_Activity_SeenMutation` from `@/generated/graphql`\n * with a Truth SDK equivalent without touching `react.ts` exports (the\n * central agent wires those).\n *\n * Pattern mirrors the existing hooks in `./conversations.ts`:\n * - `makeFunctionReference` for decoupled Convex references\n * - SKIP sentinel for conditional execution\n * - Returns a stable callback, not a live subscription\n *\n * Must be used within `<TruthProvider />`.\n */\n\n\"use client\";\n\nimport { useMutation } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport { useCallback } from \"react\";\n\n// ---------------------------------------------------------------------------\n// Convex mutation reference — decoupled from the consuming app's codegen\n// ---------------------------------------------------------------------------\n\nconst conversationTasksMarkSeenRef = makeFunctionReference<\n \"mutation\",\n { taskId: string; userId: string },\n { ok: true }\n>(\"conversationTasks:markSeen\");\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a stable `markSeen(taskId, userId)` callback backed by the\n * `conversationTasks:markSeen` Convex mutation. Replaces the Hasura\n * `useMark_Event_Activity_SeenMutation` in CommHub's My Tasks surface.\n *\n * @example\n * ```tsx\n * const markSeen = useConversationTaskMarkSeen();\n *\n * // When a task row is pressed:\n * if (!task.seenBy.includes(userId)) {\n * markSeen(task.id, userId).catch(console.error);\n * }\n * ```\n */\nfunction useConversationTaskMarkSeen(): (\n taskId: string,\n userId: string,\n) => Promise<{ ok: true }> {\n const mutate = useMutation(\n conversationTasksMarkSeenRef as FunctionReference<\"mutation\">,\n );\n\n return useCallback(\n (taskId: string, userId: string) =>\n mutate({ taskId, userId }) as Promise<{ ok: true }>,\n [mutate],\n );\n}\n\nexport { useConversationTaskMarkSeen };\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 // Fall back to `EXPO_PUBLIC_TRUTH_API_KEY` when the prop isn't passed\n // so consumers can mount this provider with just `environment` set.\n const resolvedApiKey =\n apiKey ??\n (typeof process !== \"undefined\"\n ? process.env?.EXPO_PUBLIC_TRUTH_API_KEY\n : undefined) ??\n \"\";\n const value = useMemo<TruthTrackingContextValue>(() => {\n const tracker = new Tracker({\n apiKey: resolvedApiKey,\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 }, [resolvedApiKey, 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 * 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 type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UserSettings } from \"../resources/user-settings\";\nimport type { UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist: user settings render from the mirror offline.\n// Aliased so the call site below is unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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 * useUserSync — mirror the caller's Clerk profile into Truth's user roster.\n *\n * Truth's `users` Convex table backs the SMS push fan-out (office cohort\n * + `notifications_enabled`), assignee pickers, and office-scoped\n * filters. CommHub mounts this hook on app start so the active user\n * lands in Truth without anyone reaching into Hasura.\n *\n * Resolves `apiBaseUrl` + `apiKey` from `<TruthProvider>` context, so\n * the call site only passes the per-user details:\n *\n * ```tsx\n * useUserSync({ userId, email, name, imageUrl });\n * ```\n *\n * Idempotent — fires once per (userId, email, name, imageUrl) change.\n */\n\n\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useTruthSdkContext } from \"./provider\";\n\nexport interface UseUserSyncInput {\n /** Truth API base URL. Defaults to TruthProvider context. */\n apiBaseUrl?: string;\n /** `hn_live_*` application key. Defaults to TruthProvider context. */\n apiKey?: string;\n /** Clerk userId. The hook is a no-op while this is nullish. */\n userId: string | null | undefined;\n email?: string;\n name?: string;\n firstName?: string;\n lastName?: string;\n imageUrl?: string;\n notificationsEnabled?: boolean;\n}\n\nexport interface UseUserSyncResult {\n /** \"idle\" before any sync, \"syncing\" mid-fetch, \"synced\" on success, \"error\" otherwise. */\n status: \"idle\" | \"syncing\" | \"synced\" | \"error\";\n /** Last error message when status === \"error\". */\n error: string | null;\n /** Manually re-run the sync. Usually not needed — the hook auto-syncs. */\n sync: () => Promise<{ ok: boolean; reason?: string }>;\n}\n\nexport function useUserSync(input: UseUserSyncInput): UseUserSyncResult {\n const sdkContext = useTruthSdkContext();\n const apiBaseUrl = input.apiBaseUrl ?? sdkContext?.apiBaseUrl ?? \"\";\n const apiKey = input.apiKey ?? sdkContext?.apiKey ?? \"\";\n\n const [status, setStatus] = useState<UseUserSyncResult[\"status\"]>(\"idle\");\n const [error, setError] = useState<string | null>(null);\n // Track the last-synced payload so we don't fire on every re-render.\n const lastKeyRef = useRef<string | null>(null);\n\n const sync = async (): Promise<{ ok: boolean; reason?: string }> => {\n if (!input.userId) {\n return { ok: false, reason: \"missing_userId\" };\n }\n if (!apiBaseUrl || !apiKey) {\n return { ok: false, reason: \"missing_truth_config\" };\n }\n setStatus(\"syncing\");\n setError(null);\n try {\n const res = await fetch(`${apiBaseUrl}/api/users/sync`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify({\n userId: input.userId,\n email: input.email,\n name: input.name,\n firstName: input.firstName,\n lastName: input.lastName,\n imageUrl: input.imageUrl,\n notificationsEnabled: input.notificationsEnabled,\n }),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n const reason = `sync_failed_${res.status}: ${text.slice(0, 120)}`;\n setStatus(\"error\");\n setError(reason);\n return { ok: false, reason };\n }\n setStatus(\"synced\");\n return { ok: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n setStatus(\"error\");\n setError(message);\n return { ok: false, reason: message };\n }\n };\n\n useEffect(() => {\n if (!input.userId) {\n return;\n }\n const key = [\n // Include the resolved config so a first mount with missing creds\n // (sync no-ops as \"missing_truth_config\") doesn't pin the key and\n // permanently skip the valid retry once creds arrive.\n apiBaseUrl,\n apiKey,\n input.userId,\n input.email ?? \"\",\n input.name ?? \"\",\n input.firstName ?? \"\",\n input.lastName ?? \"\",\n input.imageUrl ?? \"\",\n input.notificationsEnabled === undefined\n ? \"\"\n : String(input.notificationsEnabled),\n ].join(\"|\");\n if (key === lastKeyRef.current) {\n return;\n }\n lastKeyRef.current = key;\n void sync();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n apiBaseUrl,\n apiKey,\n input.userId,\n input.email,\n input.name,\n input.firstName,\n input.lastName,\n input.imageUrl,\n input.notificationsEnabled,\n ]);\n\n return { status, error, sync };\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) {\n return null;\n }\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,qBAAAA;AAAA,EAAA;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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACwBA,mBAAyC;AACzC,oBAAsC;;;ACKtC,yBAA4B;AAC5B,IAAAC,sBAA6C;AAWtC,SAAS,mBACd,KACA,MACkC;AAOlC,QAAM,EAAE,KAAK,QAAI,oBAAAC;AAAA,QACf;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ADrBO,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,aAAAC,UAAa,eAAe,4BAAW,CAAC,CAAC;AACxD,SAAO,SAAS,QAAQ,KAAK;AAC/B;AAMO,SAAS,+BACd,gBACA,SACkC;AAClC,QAAM,UAAU,CAAC;AACjB,QAAM,SAAS;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,SAAS;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,SAAS;AAAA,IACb;AAAA,IACA,UAAU,OAAO,EAAE,QAA0B,OAAO,mCAAS,MAAM;AAAA,EACrE;AACA,SAAO,SAAS,QAAQ,OAAO;AACjC;;;AE3MA,IAAAC,iBAAsC;AAUtC,IAAM,8BAA0B,sCAI9B,uBAAuB;AAczB,SAAS,oBACP,IACwC;AACxC,QAAM,UAAU,CAAC;AACjB,QAAM,SAAS;AAAA,IACb;AAAA,IACA,UAAU,SAAS,EAAE,GAAiB;AAAA,EACxC;AAEA,MAAI,SAAS;AACX,WAAO,EAAE,MAAM,QAAW,SAAS,OAAO,OAAO,OAAU;AAAA,EAC7D;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,WAAW;AAAA,IACpB,OAAO;AAAA,EACT;AACF;;;ACVA,IAAAC,iBAAsC;AACtC,IAAAC,gBAAwB;AAmNxB,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,IAAMC,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;AAja1C;AAkaE,QAAM,iBAAgB,mBAAQ,WAAR,mBAAgB,WAAhB,YAA0B;AAChD,QAAM,eAAe,cAAc,SAAS;AAC5C,QAAM,UAAU,CAAC,QAAQ;AAEzB,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,WAAW,eACPD,QACA;AAAA,MACE,QAAQ,QAAQ;AAAA,MAChB,OAAO,QAAQ;AAAA,IACjB;AAAA,EACN;AAEA,QAAM,eAAe;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,SAAS;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,SAAS;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,SAAS;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,SAAS;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,OAAO,iCAAQ,UAAS,CAAC,GAAG,MAAM,EAAE,KAAK,IAAI;AAAA;AAAA,IAE7C,CAAC,GAAG;AAAA,EACN;AACF;AAOA,SAAS,qBACP,gBACuC;AACvC,QAAM,UAAU,CAAC;AACjB,QAAM,SAAS;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,SAAS;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,SAAS;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,SAAS;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,SAAS;AAAA,IACb;AAAA,IACA,UAAUD,QAAO,EAAE,UAA+B;AAAA,EACpD;AAEA,SAAOC,UAAS,QAAQ,OAAO;AACjC;;;ACnmBA,IAAAC,gBAA0B;;;ACQ1B,IAAAC,sBAAkC;AAClC,IAAAA,sBAAiD;AACjD,wCAA2C;AAC3C,IAAAC,gBAAkD;AAElD,IAAAA,gBAOO;;;AC3BP,qBAA2D;AASpD,IAAM,4BAAN,MAAM,mCAAkC,gCAAiB;AAAA,EAG9D,YAAY,SAAiB,cAAiC;AAC5D,UAAM,OAAO;AACb,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOc,WAA0B;AAAA;AACtC,UAAI,CAAC,KAAK,cAAc;AACtB;AAAA,MACF;AACA,UAAI;AACF,cAAM,QAAQ,MAAM,KAAK,aAAa;AACtC,YAAI,OAAO;AACT,eAAK,QAAQ,KAAK;AAAA,QACpB,OAAO;AACL,eAAK,UAAU;AAAA,QACjB;AAAA,MACF,SAAQ;AAAA,MAER;AAAA,IACF;AAAA;AAAA,EAEe,MACb,UACG,MACiC;AAAA;AACpC,YAAM,KAAK,SAAS;AACpB,aAAO,uDAAM,cAAN,MAAY,OAAO,GAAG,IAAI;AAAA,IACnC;AAAA;AAAA,EAEe,SACb,aACG,MACoC;AAAA;AACvC,YAAM,KAAK,SAAS;AACpB,aAAO,uDAAM,iBAAN,MAAe,UAAU,GAAG,IAAI;AAAA,IACzC;AAAA;AAAA,EAEe,OACb,WACG,MACkC;AAAA;AACrC,YAAM,KAAK,SAAS;AACpB,aAAO,uDAAM,eAAN,MAAa,QAAQ,GAAG,IAAI;AAAA,IACrC;AAAA;AACF;;;AChEA,IAAM,sBAAN,MAA0B;AAAA,EAGxB,YAAY,cAAgC;AAC1C,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKM,IAAI,IAAyC;AAAA;AACjD,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,OAAO;AAAA,UAC/B;AAAA,UACA,EAAE,GAAG;AAAA,QACP;AACA,eAAQ,0BAA0B;AAAA,MACpC,SAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,KACJ,SACuC;AAAA;AACvC,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,OAAO;AAAA,UAC/B;AAAA,UACA;AAAA,YACE,WAAW,mCAAS;AAAA,YACpB,WAAW,mCAAS;AAAA,YACpB,SAAS,mCAAS;AAAA,YAClB,QAAQ,mCAAS;AAAA,YACjB,OAAO,mCAAS;AAAA,YAChB,QAAQ,mCAAS;AAAA,UACnB;AAAA,QACF;AAEA,cAAM,QAAQ;AAEd,eAAO,wBAAS,EAAE,MAAM,CAAC,GAAG,QAAQ,MAAM,SAAS,MAAM;AAAA,MAC3D,SAAQ;AACN,eAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,MAAM,SAAS,MAAM;AAAA,MAClD;AAAA,IACF;AAAA;AACF;;;ACNA,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAGnC,YAAY,WAAmB,QAAgB,SAAkB;AAC/D,UAAM,4BAAW,cAAc,SAAS,iBAAiB,MAAM,GAAG;AAClE,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAEA,IAAM,sBAAN,MAA0B;AAAA,EAKxB,YACE,YACA,QACA,cACA;AACA,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,SAAS;AAAA,EAChB;AAAA,EAEc,KAAQ,MAAc,MAA2B;AAAA;AAC7D,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,OAAO,IAAI,IAAI;AAAA,QACpD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI,iBAAiB,MAAM,IAAI,QAAQ,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MACjE;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AAAA,EAEM,gBACJ,OACgC;AAAA;AAChC,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AAAA,EAEM,eACJ,OACA,WAC+B;AAAA;AAC/B,aAAO,MAAM,KAAK,KAA2B,6BAA6B;AAAA,QACxE;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA;AAAA,EAEM,OACJ,OACkD;AAAA;AAClD,aAAQ,MAAM,KAAK,OAAO;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AAAA,EAEM,IAAI,cAAkD;AAAA;AAC1D,UAAI;AACF,cAAM,MAAO,MAAM,KAAK,OAAO;AAAA,UAC7B;AAAA,UACA,EAAE,aAAa;AAAA,QACjB;AACA,eAAO,oBAAO;AAAA,MAChB,SAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA,EAEM,mBAAmB,gBAA+C;AAAA;AACtE,UAAI;AACF,cAAM,OAAQ,MAAM,KAAK,OAAO;AAAA,UAC9B;AAAA,UACA,EAAE,eAAe;AAAA,QACnB;AACA,eAAO,sBAAQ,CAAC;AAAA,MAClB,SAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYM,OAAO,OAaV;AAAA;AAxKL;AAyKI,YAAM,YAAY,MAAM,KAAK,gBAAgB;AAAA,QAC3C,UAAU,MAAM;AAAA,QAChB,UAAU,MAAM;AAAA,QAChB,MAAM,MAAM;AAAA,QACZ,gBAAgB,MAAM;AAAA,MACxB,CAAC;AAMD,YAAM,OACJ,MAAM,gBAAgB,OAClB,MAAM,OACN,MAAM,gBAAgB,aACnB,MAAM,OACN,IAAI,WAAW,MAAM,IAAI;AAIlC,YAAM,QAAQ,IAAI,gBAAgB;AAClC,YAAM,QAAQ,WAAW,MAAM,MAAM,MAAM,GAAG,GAAM;AACpD,UAAI;AACJ,UAAI;AACF,iBAAS,MAAM,MAAM,UAAU,WAAW;AAAA,UACxC,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,MAAM,SAAS;AAAA,UAC1C;AAAA,UACA,QAAQ,MAAM;AAAA,QAChB,CAAC;AAAA,MACH,SAAS,KAAK;AAKZ,cAAM,UACJ,eAAe,UACd,IAAI,SAAS,gBAAgB,IAAI,SAAS;AAC7C,YAAI,SAAS;AACX,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,cAAM;AAAA,MACR,UAAE;AACA,qBAAa,KAAK;AAAA,MACpB;AACA,UAAI,CAAC,OAAO,IAAI;AACd,cAAM,IAAI;AAAA,UACR;AAAA,UACA,OAAO;AAAA,UACP,UAAU,OAAO,MAAM,QAAQ,UAAU,KAAK;AAAA,QAChD;AAAA,MACF;AAEA,YAAM,WAAW,MAAM,KAAK,OAAO;AAAA,QACjC,OAAO,UAAU;AAAA,QACjB,UAAU,MAAM;AAAA,QAChB,UAAU,MAAM;AAAA,QAChB,MAAM,MAAM;AAAA,QACZ,gBAAgB,MAAM;AAAA,QACtB,YAAY,MAAM;AAAA,MACpB,CAAC;AAED,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB,UAAU;AAAA,SACV,WAAM,sBAAN,YAA2B,IAAI,KAAK;AAAA,MACtC;AAEA,aAAO;AAAA,QACL,cAAc,SAAS;AAAA,QACvB,OAAO,UAAU;AAAA,QACjB,aAAa,OAAO;AAAA,MACtB;AAAA,IACF;AAAA;AACF;;;AClJO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAG5C,YAAY,WAAmB,QAAgB,SAAkB;AAC/D,UAAM,4BAAW,iBAAiB,SAAS,iBAAiB,MAAM,GAAG;AACrE,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAOA,SAAS,0BACP,WACA,OACM;AACN,MAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,WAAW;AAC7C,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAMA,IAAM,+BAAN,MAAmC;AAAA,EACjC,YACmB,MACjB;AADiB;AAAA,EAChB;AAAA;AAAA,EAGG,OAAO,OAA6D;AAAA;AACxE,gCAA0B,gBAAgB,KAAK;AAC/C,aAAO,KAAK,KAAqB,wBAAwB,KAAK;AAAA,IAChE;AAAA;AACF;AAEA,IAAM,+BAAN,MAAmC;AAAA,EACjC,YACmB,MACA,OACjB;AAFiB;AACA;AAAA,EAChB;AAAA;AAAA,EAGG,OAAO,OAA6D;AAAA;AACxE,gCAA0B,gBAAgB,KAAK;AAC/C,aAAO,KAAK,KAAqB,wBAAwB,KAAK;AAAA,IAChE;AAAA;AAAA;AAAA,EAGM,UAAU,OAEb;AAAA;AACD,aAAO,KAAK;AAAA,QACV,wBAAwB,mBAAmB,MAAM,MAAM,CAAC;AAAA,QACxD;AAAA,UACE,IAAI,MAAM;AAAA,UACV,QAAQ,MAAM;AAAA,WACV,MAAM,aAAa,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC,IACvD,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,MAEhD;AAAA,IACF;AAAA;AAAA;AAAA,EAGM,YAAY,OAGf;AAAA;AACD,aAAO,KAAK;AAAA,QACV,wBAAwB,mBAAmB,MAAM,MAAM,CAAC;AAAA,QACxD;AAAA,UACE,IAAI,MAAM;AAAA,UACV,UAAU,MAAM;AAAA,WACZ,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,MAEhD;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,OAAO,OAA2D;AAAA;AACtE,aAAO,KAAK;AAAA,QACV,wBAAwB,mBAAmB,MAAM,MAAM,CAAC;AAAA,QACxD;AAAA,UACE,IAAI,MAAM;AAAA,UACV,gBAAgB,MAAM;AAAA,UACtB,QAAQ,MAAM;AAAA,UACd,aAAa,MAAM;AAAA,WACf,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC,IACtD,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC,IACjD,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC,IAC3C,MAAM,aAAa,SAAY,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC,IAC/D,MAAM,SAAS,SAAY,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC,IACnD,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,MAEhD;AAAA,IACF;AAAA;AACF;AAEA,IAAM,kCAAN,MAAsC;AAAA,EACpC,YACmB,MACjB;AADiB;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASG,KACJ,OACwC;AAAA;AACxC,UAAI,CAAC,MAAM,WAAW,CAAC,MAAM,OAAO;AAClC,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,aAAO,KAAK;AAAA,QACV;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AACF;AAMO,IAAM,yBAAN,MAAM,uBAAsB;AAAA,EAYjC,YACE,YACA,QACA,QACA;AACA,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,SAAS,0BAAU;AACxB,UAAM,OAAO,CAAI,MAAc,SAC7B,KAAK,YAAe,MAAM,IAAI;AAChC,UAAM,QAAQ,CAAI,MAAc,SAC9B,KAAK,aAAgB,MAAM,IAAI;AACjC,SAAK,QAAQ,IAAI,6BAA6B,IAAI;AAClD,SAAK,QAAQ,IAAI,6BAA6B,MAAM,KAAK;AACzD,SAAK,WAAW,IAAI,gCAAgC,IAAI;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,SAAS,OAGW;AAAA;AACxB,UAAI,CAAC,KAAK,QAAQ;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,aAAQ,MAAM,KAAK,OAAO;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,WAAW,OAGc;AAAA;AAC7B,UAAI,CAAC,KAAK,QAAQ;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,aAAQ,MAAM,KAAK,OAAO;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,eAAe,OAGY;AAAA;AAC/B,UAAI,CAAC,KAAK,QAAQ;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,aAAQ,MAAM,KAAK,OAAO;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AAAA,EAEc,YAAe,MAAc,MAA2B;AAAA;AACpE,UAAI,CAAC,KAAK,QAAQ;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAEA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,UAAU;AAAA,QACd,MAAM,WAAW,MAAM;AAAA,QACvB,uBAAsB;AAAA,MACxB;AAEA,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,MAAM,GAAG,KAAK,OAAO,OAAO,IAAI,IAAI;AAAA,UAC9C,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,QAAQ;AAAA,YACR,aAAa,KAAK;AAAA,UACpB;AAAA,UACA,MAAM,KAAK,UAAU,IAAI;AAAA,UACzB,QAAQ,WAAW;AAAA,QACrB,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,cAAM,UACJ,eAAe,UACd,IAAI,SAAS,gBAAgB,IAAI,SAAS;AAC7C,cAAM,UAAU,UACZ,iBAAiB,IAAI,oBAAoB,uBAAsB,kBAAkB,OACjF,eAAe,QACb,IAAI,UACJ;AACN,cAAM,IAAI,mBAAmB,MAAM,GAAG,OAAO;AAAA,MAC/C,UAAE;AACA,qBAAa,OAAO;AAAA,MACtB;AAEA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI,mBAAmB,MAAM,IAAI,QAAQ,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MACnE;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQc,aAAgB,MAAc,MAA2B;AAAA;AACrE,UAAI,CAAC,KAAK,QAAQ;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,UAAU;AAAA,QACd,MAAM,WAAW,MAAM;AAAA,QACvB,uBAAsB;AAAA,MACxB;AACA,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,MAAM,GAAG,KAAK,OAAO,OAAO,IAAI,IAAI;AAAA,UAC9C,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,QAAQ;AAAA,YACR,aAAa,KAAK;AAAA,UACpB;AAAA,UACA,MAAM,KAAK,UAAU,IAAI;AAAA,UACzB,QAAQ,WAAW;AAAA,QACrB,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,cAAM,UACJ,eAAe,UACd,IAAI,SAAS,gBAAgB,IAAI,SAAS;AAC7C,cAAM,UAAU,UACZ,iBAAiB,IAAI,oBAAoB,uBAAsB,kBAAkB,OACjF,eAAe,QACb,IAAI,UACJ,OAAO,GAAG;AAChB,cAAM,IAAI,mBAAmB,MAAM,GAAG,OAAO;AAAA,MAC/C,UAAE;AACA,qBAAa,OAAO;AAAA,MACtB;AACA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI;AAAA,UACR;AAAA,UACA,IAAI;AAAA,UACJ,iBAAiB,IAAI,YAAY,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,QACrD;AAAA,MACF;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AACF;AAAA;AAlMa,uBAMa,qBAAqB;AANxC,IAAM,wBAAN;;;AChLP,IAAM,kBAAN,MAAsB;AAAA,EAIpB,YAAY,YAAoB,QAAgB;AAC9C,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKM,QAAQ,QAAiD;AAAA;AAC7D,YAAM,OAAO;AAAA,QACX,YAAY,CAAC,OAAO,SAAS;AAAA,QAC7B,aAAa,OAAO;AAAA,QACpB,oBAAoB;AAAA,SAChB,OAAO,UAAU,EAAE,MAAM,OAAO,QAAQ,IAAI,CAAC,IAC7C,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;AAGhD,aAAO,KAAK,KAAsB,QAAQ,IAAI;AAAA,IAChD;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,aACJ,QACA,aAC+B;AAAA;AAC/B,aAAO,KAAK,KAA2B,UAAU,MAAM,kBAAkB;AAAA,QACvE,cAAc;AAAA,MAChB,CAAC;AAAA,IACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,WAAW,QAA+B;AAAA;AAC9C,YAAM,KAAK,IAAI,SAAS,MAAM,iBAAiB;AAAA,IACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,QAAQ,QAA+B;AAAA;AAC3C,YAAM,KAAK,WAAW,MAAM;AAAA,IAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUM,sBAAsB,QAKC;AAAA;AAC3B,aAAO,KAAK,QAAQ;AAAA,QAClB,aAAa,OAAO;AAAA,QACpB,WAAW,OAAO;AAAA,QAClB,SAAS,OAAO;AAAA,QAChB,OAAO,CAAC,OAAO,QAAQ;AAAA,MACzB,CAAC;AAAA,IACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,cAAc,QAAoD;AAAA;AACtE,UAAI;AACF,eAAO,MAAM,KAAK,IAAwB,SAAS,MAAM,EAAE;AAAA,MAC7D,SAAS,OAAO;AACd,YAAI,iBAAiB,qBAAqB,MAAM,WAAW,KAAK;AAC9D,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,QAAQ,QAAsD;AAAA;AAClE,UAAI;AACF,eAAO,MAAM,KAAK,IAAiB,UAAU,MAAM,EAAE;AAAA,MACvD,SAAS,OAAO;AACd,YAAI,iBAAiB,qBAAqB,MAAM,WAAW,KAAK;AAC9D,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,eAAe,OAA4C;AAAA;AA3KnE;AA4KI,YAAM,SAAS,MAAM,KAAK,IAA6B,UAAU;AAAA,QAC/D;AAAA,MACF,CAAC;AACD,cAAO,kBAAO,UAAP,mBAAe,OAAf,YAAqB;AAAA,IAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,qBAAqB,aAAkD;AAAA;AArL/E;AAsLI,YAAM,SAAS,MAAM,KAAK,IAA6B,UAAU;AAAA,QAC/D,QAAQ;AAAA,MACV,CAAC;AACD,cAAO,kBAAO,UAAP,mBAAe,OAAf,YAAqB;AAAA,IAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,cAAc,aAAwD;AAAA;AAC1E,UAAI;AACF,cAAM,cAAc,YAAY,QAAQ,WAAW,EAAE;AACrD,eAAO,MAAM,KAAK;AAAA,UAChB,YAAY,mBAAmB,WAAW,CAAC;AAAA,QAC7C;AAAA,MACF,SAAS,OAAO;AACd,YAAI,iBAAiB,qBAAqB,MAAM,WAAW,KAAK;AAC9D,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,sBAAsB,eAA+C;AAAA;AACzE,YAAM,MAAM,GAAG,KAAK,OAAO;AAE3B,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,gBAAgB,cAAc,CAAC;AAAA,MACxD,CAAC;AAED,YAAM,SAAU,MAAM,SAAS,KAAK;AAEpC,UAAI,CAAC,SAAS,MAAM,CAAC,OAAO,SAAS;AACnC,eAAO;AAAA,MACT;AAEA,aAAO,OAAO;AAAA,IAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMc,IACZ,MACA,QACY;AAAA;AACZ,YAAM,MAAM,IAAI,IAAI,wBAAwB,IAAI,IAAI,KAAK,OAAO;AAChE,UAAI,QAAQ;AACV,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,cAAI,UAAU,QAAW;AACvB,gBAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,UACzC;AAAA,QACF;AAAA,MACF;AAEA,YAAM,WAAW,MAAM,MAAM,IAAI,SAAS,GAAG;AAAA,QAC3C,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,MACF,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,kBAAkB,OAAO,MAAM,SAAS,MAAM;AAAA,MAC1D;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B;AAAA;AAAA,EAEc,KAAQ,MAAc,MAA4B;AAAA;AAC9D,YAAM,MAAM,GAAG,KAAK,OAAO,wBAAwB,IAAI;AAEvD,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,SAAS,SAAY,KAAK,UAAU,IAAI,IAAI;AAAA,MACpD,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,kBAAkB,QAAQ,MAAM,SAAS,MAAM;AAAA,MAC3D;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B;AAAA;AAAA,EAEc,IAAc,MAAc,MAA4B;AAAA;AA1RxE;AA2RI,YAAM,MAAM,GAAG,KAAK,OAAO,wBAAwB,IAAI;AAEvD,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,SAAS,SAAY,KAAK,UAAU,IAAI,IAAI;AAAA,MACpD,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,kBAAkB,OAAO,MAAM,SAAS,MAAM;AAAA,MAC1D;AAEA,WAAI,cAAS,QAAQ,IAAI,cAAc,MAAnC,mBAAsC,SAAS,SAAS;AAC1D,eAAQ,MAAM,SAAS,KAAK;AAAA,MAC9B;AAEA,aAAO;AAAA,IACT;AAAA;AACF;AAMA,IAAM,mBAAN,MAAuB;AAAA,EAKrB,YAAY,YAAoB,QAAgB;AAC9C,SAAK,UAAU,IAAI,gBAAgB,YAAY,MAAM;AACrD,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeM,gBAAgB,eAGnB;AAAA;AACD,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,oCAAoC;AAAA,QACzE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,cAAc,CAAC;AAAA,MACxC,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI;AAAA,UACR,yCAAyC,IAAI,MAAM,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,QAC7E;AAAA,MACF;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUM,QAAQ,QAGX;AAAA;AACD,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,gCAAgC;AAAA,QACrE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA,MACjC,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI;AAAA,UACR,iCAAiC,IAAI,MAAM,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,QACrE;AAAA,MACF;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AACF;AAMA,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAKpC,YAAY,QAAgB,MAAc,QAAgB;AACxD;AAAA,MACE,wBAAwB,MAAM,yBAAyB,IAAI,aAAa,MAAM;AAAA,IAChF;AACA,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;;;ACnYA,IAAM,mBAAN,MAAuB;AAAA,EAKrB,YAAY,YAAoB,UAAkB,QAAgB;AAChE,SAAK,UAAU;AACf,SAAK,WAAW;AAChB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEQ,QAAQ,UAA2C;AACzD,UAAM,UAAkC;AAAA,MACtC,QAAQ;AAAA,MACR,aAAa,KAAK;AAAA,IACpB;AACA,QAAI,UAAU;AACZ,cAAQ,cAAc,IAAI;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AAAA,EAEc,QACZ,QACA,MACA,MACA,QACY;AAAA;AACZ,YAAM,MAAM,IAAI,IAAI,YAAY,KAAK,QAAQ,GAAG,IAAI,IAAI,KAAK,OAAO;AACpE,UAAI,QAAQ;AACV,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,cAAI,UAAU,QAAW;AACvB,gBAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,UACzC;AAAA,QACF;AAAA,MACF;AAEA,YAAM,WAAW,MAAM,MAAM,IAAI,SAAS,GAAG;AAAA,QAC3C;AAAA,QACA,SAAS,KAAK,QAAQ,SAAS,MAAS;AAAA,QACxC,MAAM,SAAS,SAAY,KAAK,UAAU,IAAI,IAAI;AAAA,MACpD,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,cAAc,KAAK,UAAU,QAAQ,MAAM,SAAS,MAAM;AAAA,MACtE;AAEA,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,IACJ,MACA,QACY;AAAA;AACZ,aAAO,KAAK,QAAW,OAAO,MAAM,QAAW,MAAM;AAAA,IACvD;AAAA;AAAA;AAAA,EAGM,KAAkB,MAAc,MAA4B;AAAA;AAChE,aAAO,KAAK,QAAW,QAAQ,MAAM,IAAI;AAAA,IAC3C;AAAA;AAAA;AAAA,EAGM,IAAiB,MAAc,MAA4B;AAAA;AAC/D,aAAO,KAAK,QAAW,OAAO,MAAM,IAAI;AAAA,IAC1C;AAAA;AAAA;AAAA,EAGM,MAAmB,MAAc,MAA4B;AAAA;AACjE,aAAO,KAAK,QAAW,SAAS,MAAM,IAAI;AAAA,IAC5C;AAAA;AAAA;AAAA,EAGM,OAAoB,MAA0B;AAAA;AAClD,aAAO,KAAK,QAAW,UAAU,IAAI;AAAA,IACvC;AAAA;AACF;AAMA,IAAM,cAAN,MAAkB;AAAA,EAOhB,YAAY,YAAoB,QAAgB;AAC9C,SAAK,UAAU,IAAI,iBAAiB,YAAY,WAAW,MAAM;AACjE,SAAK,OAAO,IAAI,iBAAiB,YAAY,QAAQ,MAAM;AAAA,EAC7D;AACF;AAMA,IAAM,gBAAN,cAA4B,MAAM;AAAA,EAMhC,YAAY,UAAkB,QAAgB,MAAc,QAAgB;AAC1E;AAAA,MACE,oBAAoB,MAAM,aAAa,QAAQ,GAAG,IAAI,aAAa,MAAM;AAAA,IAC3E;AACA,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;;;AC/GA,IAAM,aAAN,cAAyB,MAAM;AAAA,EAG7B,YAAY,WAAmB,QAAgB,SAAkB;AAC/D,UAAM,4BAAW,SAAS,SAAS,iBAAiB,MAAM,GAAG;AAC7D,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAyBA,IAAM,iBAAN,MAAM,eAAc;AAAA,EAIlB,YAAY,YAAoB,QAAgB;AAC9C,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA,EAMc,KAAQ,MAAc,MAA2B;AAAA;AAC7D,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,UAAU;AAAA,QACd,MAAM,WAAW,MAAM;AAAA,QACvB,eAAc;AAAA,MAChB;AAEA,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,MAAM,GAAG,KAAK,OAAO,OAAO,IAAI,IAAI;AAAA,UAC9C,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,QAAQ;AAAA,YACR,aAAa,KAAK;AAAA,UACpB;AAAA,UACA,MAAM,KAAK,UAAU,IAAI;AAAA,UACzB,QAAQ,WAAW;AAAA,QACrB,CAAC;AAAA,MACH,SAAS,KAAK;AAIZ,cAAM,UACJ,eAAe,UACd,IAAI,SAAS,gBAAgB,IAAI,SAAS;AAC7C,cAAM,UAAU,UACZ,SAAS,IAAI,oBAAoB,eAAc,kBAAkB,OACjE,eAAe,QACb,IAAI,UACJ;AACN,cAAM,IAAI,WAAW,MAAM,GAAG,OAAO;AAAA,MACvC,UAAE;AACA,qBAAa,OAAO;AAAA,MACtB;AAEA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI,WAAW,MAAM,IAAI,QAAQ,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MAC3D;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,cACJ,OACkC;AAAA;AAClC,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASM,0BACJ,OAC0C;AAAA;AAC1C,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AACF;AAAA;AAAA;AArFM,eAWoB,qBAAqB;AAX/C,IAAM,gBAAN;;;ACkEA,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAErC,YAAY,WAAmB,QAAgB,SAAkB;AAC/D,UAAM,4BAAW,iBAAiB,SAAS,iBAAiB,MAAM,GAAG;AACrE,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAEA,IAAM,wBAAN,MAA4B;AAAA,EAI1B,YAAY,YAAoB,QAAgB;AAC9C,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA,EAEc,KAAQ,MAAc,MAA2B;AAAA;AAC7D,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,OAAO,IAAI,IAAI;AAAA,QACpD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI,mBAAmB,MAAM,IAAI,QAAQ,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MACnE;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AAAA,EAEc,IACZ,MACA,QACY;AAAA;AACZ,YAAM,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,OAAO,IAAI,EAAE;AAChD,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,YAAI,aAAa,IAAI,GAAG,CAAC;AAAA,MAC3B;AACA,YAAM,MAAM,MAAM,MAAM,IAAI,SAAS,GAAG;AAAA,QACtC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,MACF,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI,mBAAmB,MAAM,IAAI,QAAQ,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MACnE;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AAAA,EAEc,OAAU,MAA0B;AAAA;AAChD,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,OAAO,IAAI,IAAI;AAAA,QACpD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,MACF,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI,mBAAmB,MAAM,IAAI,QAAQ,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MACnE;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,eACJ,OAC+B;AAAA;AAC/B,aAAO,KAAK,KAAK,mCAAmC,KAAK;AAAA,IAC3D;AAAA;AAAA;AAAA,EAGM,iBACJ,OAC+B;AAAA;AAC/B,aAAO,KAAK,KAAK,qCAAqC,KAAK;AAAA,IAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,KAAK,OAA+D;AAAA;AACxE,aAAO,KAAK,KAAK,uBAAuB,KAAK;AAAA,IAC/C;AAAA;AAAA;AAAA,EAGM,eAAe,QAAkD;AAAA;AACrE,aAAO,KAAK,IAAI,8BAA8B,EAAE,OAAO,CAAC;AAAA,IAC1D;AAAA;AAAA,EAEM,kBACJ,OAC0B;AAAA;AAC1B,aAAO,KAAK,KAAK,8BAA8B,KAAK;AAAA,IACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUM,SACJ,OACqC;AAAA;AACrC,aAAO,KAAK,KAAK,2BAA2B,KAAK;AAAA,IACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,gBACJ,OAC4C;AAAA;AAC5C,aAAO,KAAK,OAAO,2BAA2B,mBAAmB,KAAK,CAAC,EAAE;AAAA,IAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,cACJ,QACA,SACkC;AAAA;AAClC,YAAM,SAAiC,EAAE,OAAO;AAChD,WAAI,mCAAS,WAAU,QAAW;AAChC,eAAO,QAAQ,OAAO,QAAQ,KAAK;AAAA,MACrC;AACA,aAAO,KAAK,IAAI,2BAA2B,MAAM;AAAA,IACnD;AAAA;AAAA,EAEM,cAAsC;AAAA;AAC1C,UAAI;AACF,cAAM,SAAS,MAAM,KAAK;AAAA,UACxB;AAAA,UACA,CAAC;AAAA,QACH;AACA,eAAO,OAAO;AAAA,MAChB,SAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA,EAEA,eAAe,UAA2D;AACxE,QAAI,OAAO,cAAc,eAAe,EAAE,mBAAmB,YAAY;AACvE,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AACA,UAAM,UAAU,CAAC,UAAwB;AApS7C;AAqSM,YAAI,WAAM,SAAN,mBAAY,UAAS,uBAAuB;AAC9C,iBAAS,MAAM,KAAK,OAAO;AAAA,MAC7B;AAAA,IACF;AACA,cAAU,cAAc,iBAAiB,WAAW,OAAO;AAC3D,WAAO,MACL,UAAU,cAAc,oBAAoB,WAAW,OAAO;AAAA,EAClE;AAAA,EAEA,aAAa,UAA2D;AACtE,QAAI,OAAO,cAAc,eAAe,EAAE,mBAAmB,YAAY;AACvE,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AACA,UAAM,UAAU,CAAC,UAAwB;AAlT7C;AAmTM,YAAI,WAAM,SAAN,mBAAY,UAAS,qBAAqB;AAC5C,iBAAS,MAAM,KAAK,OAAO;AAAA,MAC7B;AAAA,IACF;AACA,cAAU,cAAc,iBAAiB,WAAW,OAAO;AAC3D,WAAO,MACL,UAAU,cAAc,oBAAoB,WAAW,OAAO;AAAA,EAClE;AACF;;;AC5RA,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAGtC,YAAY,WAAmB,QAAgB,SAAkB;AAC/D,UAAM,4BAAW,WAAW,SAAS,iBAAiB,MAAM,GAAG;AAC/D,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAEA,IAAM,yBAAN,MAA6B;AAAA,EAI3B,YAAY,YAAoB,QAAgB;AAC9C,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA,EAEc,KAAQ,MAAc,MAA2B;AAAA;AAC7D,YAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,OAAO,IAAI,IAAI;AAAA,QACpD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI,oBAAoB,MAAM,IAAI,QAAQ,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MACpE;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AAAA,EAEM,IAAI,OAA2D;AAAA;AACnE,aAAO,MAAM,KAAK,KAA2B,qBAAqB,KAAK;AAAA,IACzE;AAAA;AAAA,EAEM,SACJ,OACoC;AAAA;AACpC,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AAAA,EAEM,WAAW,WAAyD;AAAA;AACxE,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACA,EAAE,UAAU;AAAA,MACd;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQM,eAAe,WAOlB;AAAA;AACD,aAAO,MAAM,KAAK,KAOf,6BAA6B,EAAE,UAAU,CAAC;AAAA,IAC/C;AAAA;AACF;;;ACrGA,IAAM,kBAAN,MAAsB;AAAA,EAGpB,YAAY,cAAgC;AAC1C,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKM,IAAI,IAAqC;AAAA;AAC7C,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,OAAO;AAAA,UAC/B;AAAA,UACA;AAAA,YACE;AAAA,UACF;AAAA,QACF;AACA,eAAQ,0BAAsB;AAAA,MAChC,SAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,eAAe,WAA4C;AAAA;AAC/D,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,OAAO;AAAA,UAC/B;AAAA,UACA,EAAE,UAAU;AAAA,QACd;AACA,eAAQ,0BAAsB;AAAA,MAChC,SAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,YAAY,QAAyC;AAAA;AACzD,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,OAAO;AAAA,UAC/B;AAAA,UACA;AAAA,YACE;AAAA,UACF;AAAA,QACF;AACA,eAAQ,0BAAsB;AAAA,MAChC,SAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,KAAK,SAAiE;AAAA;AAC1E,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,OAAO;AAAA,UAC/B;AAAA,UACA;AAAA,YACE,QAAQ,mCAAS;AAAA,YACjB,OAAO,mCAAS;AAAA,YAChB,QAAQ,mCAAS;AAAA,UACnB;AAAA,QACF;AAEA,cAAM,QAAQ;AAEd,eAAO,wBAAS,EAAE,MAAM,CAAC,GAAG,QAAQ,MAAM,SAAS,MAAM;AAAA,MAC3D,SAAQ;AACN,eAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,MAAM,SAAS,MAAM;AAAA,MAClD;AAAA,IACF;AAAA;AACF;;;AC/DA,IAAM,qBAAN,MAAyB;AAAA,EAGvB,YAAY,QAA0B;AACpC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKM,gBAAgB,KAAqC;AAAA;AACzD,UAAI,IAAI,WAAW,GAAG;AACpB,eAAO,CAAC;AAAA,MACV;AACA,UAAI;AACF,cAAM,OAAQ,MAAM,KAAK,OAAO;AAAA,UAC9B;AAAA,UACA,EAAE,IAAI;AAAA,QACR;AACA,eAAO,sBAAQ,CAAC;AAAA,MAClB,SAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAAA;AAAA,EAEM,eAAe,IAAuC;AAAA;AAC1D,UAAI;AACF,cAAM,MAAO,MAAM,KAAK,OAAO;AAAA,UAC7B;AAAA,UACA,EAAE,GAAG;AAAA,QACP;AACA,eAAO,oBAAO;AAAA,MAChB,SAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA,EAEM,eAAe,UAAkB,OAAsC;AAAA;AAC3E,UAAI;AACF,cAAM,OAAQ,MAAM,KAAK,OAAO;AAAA,UAC9B;AAAA,UACA,EAAE,UAAU,MAAM;AAAA,QACpB;AACA,eAAO,sBAAQ,CAAC;AAAA,MAClB,SAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAAA;AACF;;;ACxDA,IAAM,oBAAN,MAAwB;AAAA,EAGtB,YAAY,cAAgC;AAC1C,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,SACJ,OACiC;AAAA;AACjC,YAAM,WACJ,MAAM,oBAAoB,OACtB,MAAM,SAAS,YAAY,IAC3B,MAAM;AAEZ,YAAM,SAAU,MAAM,KAAK,OAAO;AAAA,QAChC;AAAA,QACA;AAAA,UACE,gBAAgB,MAAM;AAAA,UACtB;AAAA,UACA,MAAM,MAAM;AAAA,UACZ,WAAW,MAAM;AAAA,QACnB;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,OACJ,YACA,aACiD;AAAA;AACjD,aAAQ,MAAM,KAAK,OAAO;AAAA,QACxB;AAAA,QACA,EAAE,YAAY,YAAY;AAAA,MAC5B;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKM,mBAAmB,gBAA6C;AAAA;AACpE,UAAI;AACF,cAAM,OAAQ,MAAM,KAAK,OAAO;AAAA,UAC9B;AAAA,UACA,EAAE,eAAe;AAAA,QACnB;AACA,eAAO,sBAAQ,CAAC;AAAA,MAClB,SAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAAA;AACF;;;AC1DA,IAAM,gBAAN,MAAoB;AAAA,EAGlB,YAAY,cAAgC;AAC1C,SAAK,SAAS;AAAA,EAChB;AAAA,EAEM,OACJ,OACiD;AAAA;AACjD,aAAQ,MAAM,KAAK,OAAO;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AAAA,EAEM,aAAa,OAIhB;AAAA;AACD,aAAQ,MAAM,KAAK,OAAO;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,IAKF;AAAA;AAAA,EAEM,IAAI,QAAsC;AAAA;AAC9C,UAAI;AACF,cAAM,MAAO,MAAM,KAAK,OAAO;AAAA,UAC7B;AAAA,UACA,EAAE,OAAO;AAAA,QACX;AACA,eAAO,oBAAO;AAAA,MAChB,SAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA,EAEM,eACJ,YACA,SACiB;AAAA;AACjB,UAAI;AACF,cAAM,OAAQ,MAAM,KAAK,OAAO;AAAA,UAC9B;AAAA,UACA;AAAA,YACE;AAAA,YACA,QAAQ,mCAAS;AAAA,YACjB,OAAO,mCAAS;AAAA,UAClB;AAAA,QACF;AACA,eAAO,sBAAQ,CAAC;AAAA,MAClB,SAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAAA;AAAA,EAEM,SAAS,OAAiC;AAAA;AAC9C,UAAI;AACF,cAAM,OAAQ,MAAM,KAAK,OAAO;AAAA,UAC9B;AAAA,UACA,EAAE,MAAM;AAAA,QACV;AACA,eAAO,sBAAQ,CAAC;AAAA,MAClB,SAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUM,SAAS,OAA6D;AAAA;AAC1E,aAAQ,MAAM,KAAK,OAAO;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA;AACF;;;AC1FA,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAInC,YAAY,WAAmB,QAAgB,SAAkB;AAC/D,UAAM,4BAAW,eAAe,SAAS,iBAAiB,MAAM,GAAG;AACnE,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,YAAY;AAAA,EACnB;AACF;AAEA,IAAM,sBAAN,MAA0B;AAAA,EAIxB,YAAY,YAAoB,QAAgB;AAC9C,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA,EAEc,KAAQ,MAAc,MAA2B;AAAA;AAC7D,YAAM,MAAM,GAAG,KAAK,OAAO,OAAO,IAAI;AACtC,YAAM,MAAM,MAAM,MAAM,KAAK;AAAA,QAC3B,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI,iBAAiB,MAAM,IAAI,QAAQ,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,MACjE;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA;AAAA,EAEM,UAAU,OAAuD;AAAA;AACrE,aAAO,MAAM,KAAK,KAAwB,0BAA0B,KAAK;AAAA,IAC3E;AAAA;AAAA,EAEM,eACJ,OAC8B;AAAA;AAC9B,YAAM,WAAW,MAAM,KAAK;AAAA,QAC1B;AAAA,QACA;AAAA,MACF;AACA,aAAO,SAAS;AAAA,IAClB;AAAA;AAAA,EAEM,OAAO,MAAwC;AAAA;AACnD,aAAO,MAAM,KAAK,KAAsB,uBAAuB,EAAE,KAAK,CAAC;AAAA,IACzE;AAAA;AACF;;;AC1DA,IAAAC,iBAAsC;AA4BtC,IAAM,6BAAyB,sCAI7B,kCAAkC;AAMpC,IAAM,uBAAN,MAA2B;AAAA,EAGzB,YAAY,QAA0B;AACpC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,oBACJ,OACoC;AAAA;AACpC,aAAO,KAAK,OAAO,SAAS,wBAAwB,KAAK;AAAA,IAC3D;AAAA;AACF;;;ACpCA,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;AAMA,IAAM,qBAAqB;AAC3B,IAAM,4BAA4B;AAClC,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;;;ACtTO,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;;;ACRA,IAAM,cAAsC;AAAA,EAC1C,OAAO;AAAA,EACP,SAAS;AAAA,EACT,KAAK;AAAA,EACL,SAAS;AAAA,EACT,KAAK;AAAA,EACL,YAAY;AACd;AAMA,IAAM,cAAN,MAAkB;AAAA,EAiDhB,YAAY,QAA2B;AAJvC,SAAQ,kBAAiC;AACzC,SAAQ,gBAAsC;AArHhD;AA0HI,UAAM,aACJ,kBAAO,cAAP,YAAoB,YAAY,OAAO,WAAW,MAAlD,YAAuD,YAAY;AAKrE,SAAK,SAAS,IAAI,0BAA0B,WAAW,OAAO,YAAY;AAG1E,SAAK,UAAU,IAAI,QAAQ;AAAA,MACzB,QAAQ,OAAO;AAAA,MACf,aAAa,OAAO;AAAA,MACpB,SAAQ,YAAO,WAAP,YAAiB;AAAA,MACzB,gBAAe,YAAO,kBAAP,YAAwB;AAAA,MACvC,WAAU,YAAO,aAAP,YAAmB;AAAA,MAC7B,YAAW,YAAO,cAAP,YAAoB;AAAA,MAC/B,kBAAiB,YAAO,oBAAP,YAA0B;AAAA,MAC3C,YAAY,OAAO;AAAA,IACrB,CAAC;AAED,UAAM,SAAS,KAAK,QAAQ;AAG5B,SAAK,WAAW,IAAI,gBAAgB,KAAK,MAAM;AAC/C,SAAK,eAAe,IAAI,oBAAoB,KAAK,MAAM;AACvD,SAAK,MAAM,IAAI,YAAY,QAAQ,OAAO,MAAM;AAChD,SAAK,WAAW,IAAI,iBAAiB,QAAQ,OAAO,MAAM;AAC1D,SAAK,YAAY,IAAI,kBAAkB,KAAK,MAAM;AAClD,SAAK,cAAc,IAAI,oBAAoB,QAAQ,OAAO,MAAM;AAChE,SAAK,QAAQ,IAAI,cAAc,KAAK,MAAM;AAC1C,SAAK,iBAAiB,IAAI,uBAAuB,QAAQ,OAAO,MAAM;AACtE,SAAK,cAAc,IAAI;AAAA,MACrB;AAAA,MACA,OAAO;AAAA,MACP,KAAK;AAAA,IACP;AACA,SAAK,QAAQ,IAAI,cAAc,QAAQ,OAAO,MAAM;AACpD,SAAK,aAAa,IAAI,mBAAmB,KAAK,MAAM;AACpD,SAAK,gBAAgB,IAAI,sBAAsB,QAAQ,OAAO,MAAM;AACpE,SAAK,gBAAgB,IAAI;AAAA,MACvB;AAAA,MACA,OAAO;AAAA,MACP,KAAK;AAAA,IACP;AACA,SAAK,eAAe,IAAI,qBAAqB,KAAK,MAAM;AACxD,SAAK,sBAAqB,YAAO,sBAAP,YAA4B;AAEtD,QACE,OAAO,WAAW,eAClB,mBAAmB,KACnB,OAAO,0BAA0B,OACjC;AACA,WAAK,gBAAgB,KAAK,YAAY;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,IAAI,iBAAgC;AAClC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,eAAqC;AACvC,WAAO,KAAK;AAAA,EACd;AAAA,EAEc,cAA6B;AAAA;AACzC,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,cAAc,YAAY;AACjD,YAAI,CAAC,KAAK;AACR;AAAA,QACF;AACA,aAAK,kBAAkB;AAEvB,cAAM,eAAe,MAAM,sBAAsB,KAAK,kBAAkB;AACxE,cAAM,UAAU,cAAc;AAC9B,cAAM,eAAe,MAAM,gBAAgB,cAAc,GAAG;AAC5D,cAAM,UAAU,mBAAmB,YAAY;AAE/C,cAAM,KAAK,cAAc,eAAe;AAAA,UACtC,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,qBAAqB;AAAA,UACrB,QAAQ,UAAU;AAAA,UAClB,UAAU,KAAK,eAAe,EAAE,gBAAgB,EAAE;AAAA,QACpD,CAAC;AAAA,MACH,SAAQ;AAAA,MAER;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,IAAI,aAAqB;AACvB,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MACE,WACA,SACA,SACM;AACN,SAAK,QAAQ,MAAM,WAAW,SAAS,OAAO;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,SAAS,SAAiB,WAA4C;AACpE,SAAK,QAAQ,SAAS,EAAE,SAAS,UAAU,CAAC;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcM,QAAuB;AAAA;AAC3B,YAAM,KAAK,QAAQ,MAAM;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMM,UAAyB;AAAA;AAC7B,YAAM,KAAK,QAAQ,SAAS;AAAA,IAC9B;AAAA;AACF;;;ACvPO,IAAM,iBAAiB;;;ACfvB,IAAM,oBAAoB;AASjC,SAAS,kBAAkB,OAA0C;AACnE,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,YAAY;AAClB,SACE,OAAO,UAAU,cAAc,YAC/B,OAAO,SAAS,UAAU,SAAS,KACnC,OAAO,UAAU,gBAAgB,YACjC,UAAU,gBAAgB;AAE9B;AAOO,SAAS,uBACd,OACA,WAAmB,mBACR;AACX,SAAO;AAAA,IACL,cAAc,QAA+B;AAC3C,UAAI;AACF,cAAM,IAAI,UAAU,KAAK,UAAU,MAAM,CAAC;AAAA,MAC5C,SAAQ;AAAA,MAGR;AAAA,IACF;AAAA,IACA,gBAA6C;AAI3C,UAAI;AACF,cAAM,MAAM,MAAM,IAAI,QAAQ;AAC9B,YAAI,OAAO,MAAM;AACf,iBAAO;AAAA,QACT;AACA,cAAM,SAAS,KAAK,MAAM,GAAG;AAK7B,YAAI,CAAC,kBAAkB,MAAM,GAAG;AAC9B,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT,SAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,eAAqB;AACnB,UAAI;AACF,cAAM,OAAO,QAAQ;AAAA,MACvB,SAAQ;AAAA,MAIR;AAAA,IACF;AAAA,EACF;AACF;AASO,SAAS,qBACd,OACA,WAAmB,mBACJ;AAGf,MAAI;AACF,UAAM,MAAM,MAAM,IAAI,QAAQ;AAC9B,QAAI,OAAO,MAAM;AACf,aAAO;AAAA,IACT;AACA,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY;AAAA,EACnE,SAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC7EO,IAAM,YAAN,MAAwC;AAAA,EAC7C,MAAqB;AACnB,WAAO;AAAA,EACT;AAAA,EACA,MAAY;AAAA,EAAC;AAAA,EACb,SAAe;AAAA,EAAC;AAAA,EAChB,WAAiB;AAAA,EAAC;AACpB;AAQO,IAAM,aAA2B,IAAI,UAAU;AAG/C,SAAS,YAAY,OAAiD;AAC3E,SAAO,SAAS,QAAQ,iBAAiB;AAC3C;;;ArBTA,IAAM,qBAAqB,MAAO,KAAK,KAAK,KAAK;AAQ1C,IAAMC,eAAsC;AAAA,EACjD,OAAO;AAAA,EACP,SAAS;AAAA,EACT,KAAK;AAAA,EACL,SAAS;AAAA,EACT,KAAK;AAAA,EACL,YAAY;AACd;AAQO,IAAM,gBAAwC;AAAA,EACnD,OAAO;AAAA,EACP,SAAS;AAAA,EACT,KAAK;AAAA,EACL,SAAS;AAAA,EACT,KAAK;AAAA,EACL,YAAY;AACd;AAMO,SAAS,iBACd,aACA,UACQ;AA5FV;AA6FE,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AACA,QAAM,MAAM,oCAAe;AAC3B,UAAO,KAAAA,aAAY,GAAG,MAAf,YAAoBA,aAAY;AACzC;AAOO,SAAS,kBACd,aACA,UACQ;AA5GV;AA6GE,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AACA,QAAM,MAAM,oCAAe;AAC3B,UAAO,mBAAc,GAAG,MAAjB,YAAsB,cAAc;AAC7C;AAOA,SAAS,QAAQ,MAAkC;AACjD,MAAI,OAAO,YAAY,eAAe,CAAC,QAAQ,KAAK;AAClD,WAAO;AAAA,EACT;AACA,QAAM,IAAK,QAAQ,IAA2C,IAAI;AAClE,SAAO,OAAO,MAAM,YAAY,EAAE,SAAS,IAAI,IAAI;AACrD;AAoBA,IAAM,sBAAkB,6BAA2C,IAAI;AAQhE,SAAS,qBAAkD;AAChE,aAAO,0BAAW,eAAe;AACnC;AAOO,SAAS,kBAAgC;AApKhD;AAqKE,UAAO,yCAAW,eAAe,MAA1B,mBAA6B,iBAA7B,YAA6C;AACtD;AAMO,SAAS,oBAA6B;AA5K7C;AA6KE,UAAO,yCAAW,eAAe,MAA1B,mBAA6B,mBAA7B,YAA+C;AACxD;AAOO,SAAS,iBAA8B;AAC5C,QAAM,UAAM,0BAAW,eAAe;AACtC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,IAAI;AACb;AASA,IAAI,gBAAoC;AAQjC,SAAS,iBAA8B;AAC5C,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAsDA,SAAS,cAAc;AAAA,EACrB,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB;AACF,GAAuB;AAvRvB;AAwRE,QAAM,MAAM,iBAAiB,aAAa,SAAS;AAGnD,QAAM,sBACJ,uCACA,QAAQ,gCAAgC,MADxC,YAEA,kBAAkB,WAAW;AAG/B,QAAM,kBAAiB,+BAAU,QAAQ,2BAA2B,MAA7C,YAAkD;AAEzE,QAAM,mBAAe,uBAAQ,MAAM,IAAI,gCAAkB,GAAG,GAAG,CAAC,GAAG,CAAC;AAOpE,QAAM,sBAAkB,sBAAqC,YAAY;AACzE,kBAAgB,UAAU;AAC1B,QAAM,iBAAiB,QAAQ,YAAY;AAC3C,QAAM,yBAAqB;AAAA,IACzB,MACE,iBACI,MAAS;AAhTnB,UAAAC,KAAAC;AAgTuB,cAAAA,MAAA,OAAMD,MAAA,gBAAgB,YAAhB,gBAAAA,IAAA,0BAAN,OAAAC,MAAsC;AAAA,SACnD;AAAA,IACN,CAAC,cAAc;AAAA,EACjB;AAKA,+BAAU,MAAM;AACd,QAAI,oBAAoB;AACtB,mBAAa,QAAQ,MAAS;AA1TpC,YAAAD;AA0TwC,gBAAAA,MAAA,MAAM,mBAAmB,MAAzB,OAAAA,MAA+B;AAAA,QAAI;AAAA,IACvE,OAAO;AACL,mBAAa,UAAU;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,cAAc,kBAAkB,CAAC;AAarC,QAAM,wBAAoB;AAAA,IACxB,MAAM,IAAI,sCAAkB,YAAY;AAAA,IACxC,CAAC,YAAY;AAAA,EACf;AACA,QAAM,kBAAc,uBAAQ,MAAM;AAChC,WAAO,IAAI,gCAAY;AAAA,MACrB,gBAAgB;AAAA,QACd,SAAS;AAAA,UACP,gBAAgB,kBAAkB,OAAO;AAAA,UACzC,SAAS,kBAAkB,QAAQ;AAAA,UACnC,QAAQ;AAAA;AAAA;AAAA,UAGR,OAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,iBAAiB,CAAC;AAQtB,QAAM,yBAAqB,sBAAiC,IAAI;AAChE,+BAAU,MAAM;AACd,QAAI,mBAAmB,YAAY,mBAAmB;AACpD,wBAAkB,QAAQ,WAAW;AACrC,yBAAmB,UAAU;AAAA,IAC/B;AAAA,EACF,GAAG,CAAC,mBAAmB,WAAW,CAAC;AAKnC,QAAM,gBAAY;AAAA,IAChB,MACE,kBAAkB,CAAC,YAAY,YAAY,IACvC,uBAAuB,YAAY,IACnC;AAAA,IACN,CAAC,gBAAgB,YAAY;AAAA,EAC/B;AAEA,QAAM,kBAAc;AAAA,IAClB,MACE,IAAI,YAAY;AAAA;AAAA;AAAA;AAAA,MAId,WAAW;AAAA,MACX,QAAQ;AAAA,MACR;AAAA,MACA,YAAY;AAAA,MACZ,QAAQ,0BAAU;AAAA,MAClB,eAAe,wCAAiB;AAAA,MAChC,UAAU,8BAAY;AAAA,MACtB,uBAAuB;AAAA,MACvB,cAAc;AAAA,IAChB,CAAC;AAAA,IACH;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAKA,+BAAU,MAAM;AACd,oBAAgB;AAChB,WAAO,MAAM;AACX,UAAI,kBAAkB,aAAa;AACjC,wBAAgB;AAAA,MAClB;AAGA,WAAK,YAAY,QAAQ,EAAE,MAAM,MAAM;AAAA,MAEvC,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,iBAAa;AAAA,IACjB,OAAO;AAAA,MACL,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAMA,QAAM,iBAAa;AAAA,IACjB;AAAA,IACA,EAAE,QAAQ,aAAa;AAAA,IACvB;AAAA,EACF;AACA,QAAM,YAAY,gBACd;AAAA,IACE;AAAA,IACA;AAAA,MACE,QAAQ;AAAA,MACR,gBAAgB;AAAA,QACd;AAAA,QACA,QAAQ;AAAA;AAAA;AAAA,QAGR,QAAQ,OAAO,cAAc;AAAA,MAC/B;AAAA,IACF;AAAA,IACA;AAAA,EACF,QACA,6BAAc,yCAAqB,EAAE,QAAQ,YAAY,GAAG,UAAU;AAE1E,aAAO;AAAA,IACL,gBAAgB;AAAA,IAChB,EAAE,OAAO,WAAW;AAAA,IACpB;AAAA,EACF;AACF;;;ADnbA,IAAAE,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,SAAO,mBAAS,iBAA+C,4BAAW,CAAC,CAAC;AAC9E;AAKA,SAAS,WAAW,IAAY;AAC9B,SAAO,mBAAS,gBAA8C,EAAE,GAAG,CAAC;AACtE;AAKA,SAAS,sBAAsB,WAAmB;AAChD,SAAO,mBAAS,wBAAsD;AAAA,IACpE;AAAA,EACF,CAAC;AACH;AAKA,SAAS,mBAAmB,QAAgB;AAC1C,SAAO,mBAAS,qBAAmD;AAAA,IACjE;AAAA,EACF,CAAC;AACH;AAiBA,SAAS,gBAAgB,SAAqC;AAC5D,SAAO;AAAA,IACL;AAAA,IACA,4BAAW,CAAC;AAAA,EACd;AACF;AAKA,SAAS,eAAe,IAAY;AAClC,SAAO,mBAAS,oBAAkD,EAAE,GAAG,CAAC;AAC1E;AAKA,SAAS,0BAA0B,WAAmB;AACpD,SAAO,mBAAS,4BAA0D;AAAA,IACxE;AAAA,EACF,CAAC;AACH;AAoBA,IAAM,mCAA+B,sCAInC,4BAA4B;AAE9B,IAAM,kCAA8B,sCAIlC,2BAA2B;AAa7B,SAAS,0BAA0B,KAA2B;AAC5D,SAAO;AAAA,IACL;AAAA,IACA,OAAO,IAAI,SAAS,IAAI,EAAE,IAAI,IAAI;AAAA,EACpC;AACF;AAKA,SAAS,wBAAwB,IAAwB;AACvD,SAAO;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;AAmClD,SAAS,kBACP,WACA,SACA;AAvSF;AAwSE,QAAM,aAAa,mBAAmB;AACtC,QAAM,cAAa,wCAAS,eAAT,YAAuB,yCAAY;AACtD,QAAM,UAAS,wCAAS,WAAT,YAAmB,yCAAY;AAC9C,QAAM,cAAc;AAAA,IAClB;AAAA,IACA,cAAc,SAAY,EAAE,kBAAkB,UAAU,IAAI;AAAA,EAC9D;AAEA,QAAM,WAAW;AAAA,IACf;AAAA,IACA,cAAc,SAAY,EAAE,kBAAkB,UAAU,IAAI;AAAA,EAC9D;AAEA,QAAM,YAAY;AAAA,IAChB;AAAA,IACA,cAAc,SAAY,EAAE,kBAAkB,UAAU,IAAI;AAAA,EAC9D;AAEA,QAAM,eAAe;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,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,YAAY,QAAQ,mCAAS,WAAW,CAAC;AAExD,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;AA7azB;AA8aE,QAAM,aAAa,mBAAmB;AACtC,QAAM,cAAa,wCAAS,eAAT,YAAuB,yCAAY;AACtD,QAAM,UAAS,wCAAS,WAAT,YAAmB,yCAAY;AAC9C,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,MAAM,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI;AAAA,EACnE;AAEA,QAAM,UAAU;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,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,CAAC,MAAM,QAAQ,MAAM,WAAW,YAAY,QAAQ,mCAAS,WAAW,CAAC;AAK5E,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,SAAO;AAAA,IACL;AAAA,IACA,UAAU,EAAE,QAAQ,IAAI;AAAA,EAC1B;AACF;AAcA,SAAS,gBACP,WACA,SACA;AAhhBF;AAihBE,QAAM,aAAa,mBAAmB;AACtC,QAAM,cAAa,wCAAS,eAAT,YAAuB,yCAAY;AACtD,QAAM,UAAS,wCAAS,WAAT,YAAmB,yCAAY;AAC9C,QAAM,QAAQ;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,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,YAAY,QAAQ,mCAAS,WAAW,CAAC;AAExD,SAAO;AACT;AAqCA,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,SAAS;AAAA,IACb;AAAA,IACA,UACI;AAAA,MACE,QAAQ,MAAM;AAAA,MACd,QAAQ,MAAM;AAAA,MACd,OAAO,mCAAS;AAAA,IAClB,IACA;AAAA,EACN;AAEA,QAAM,UAAU;AAAA,IACd;AAAA,IACA,CAAC,WAAW,MAAM,iBACd,EAAE,gBAAgB,MAAM,gBAAgB,OAAO,mCAAS,MAAM,IAC9D;AAAA,EACN;AAEA,SAAO,UAAU,SAAS;AAC5B;;;AuBvnBA,IAAAC,gBAAyD;AAgBzD,SAAe,WAAoD;AAAA;AAQjE,QAAI;AAEF,aAAO,QAAQ,oBAAoB;AAAA,IACrC,SAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AA6DO,SAAS,iBACd,SACwB;AAnH1B;AAwHE,QAAM,aAAa,mBAAmB;AACtC,QAAM,cAAa,mBAAQ,eAAR,YAAsB,yCAAY,eAAlC,YAAgD;AACnE,QAAM,UAAS,mBAAQ,WAAR,YAAkB,yCAAY,WAA9B,YAAwC;AAEvD,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;AArItB,UAAAC;AAsIM,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,UAAU;AAAA,cACb;AAAA,gBACE,SAAS;AAAA,kBACP,QAAQ;AAAA,kBACR,aAAa;AAAA,gBACf;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,YAAY,MAAM,CAAC;AAEvB,QAAM,eAAW,2BAAY,MAAY;AArM3C,QAAAA,KAAAC;AAsMI,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,UAASD,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,cAAME,OAAM,MAAM;AAAA,UAChB,GAAG,UAAU;AAAA,UACb;AAAA,YACE,QAAQ;AAAA,YACR,SAAS;AAAA,cACP,gBAAgB;AAAA,cAChB,aAAa;AAAA,YACf;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,QAAOD,MAAA,QAAQ,YAAR,OAAAA,MAAoB,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;AAI9B,QAAI,CAAC,cAAc,CAAC,QAAQ;AAC1B,aAAO,EAAE,IAAI,OAAO,QAAQ,uBAAuB;AAAA,IACrD;AAEA,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,UAAU;AAAA,MACb;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa;AAAA,QACf;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;AAAA,IACA;AAAA,IACA,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,UAAU,yCAAyC;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,aAAa;AAAA,MACf;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,YAAY,QAAQ,eAAe,CAAC;AAExC,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;AAtWjD,cAAAD;AAuWU,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;AApXhB,YAAAA;AAoXmB,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;AAlYjD,cAAAA;AAmYU,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;AAhZhB,YAAAA;AAgZmB,gBAAAA,MAAA,IAAI,WAAJ,gBAAAA,IAAA;AAAA;AAAA,IACf;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,oBAAgB,2BAAY,MAA6B;AArZjE,QAAAA;AAsZI,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;AAuCO,SAAS,0BAAmD;AA9enE;AA+eE,QAAM,aAAa,mBAAmB;AACtC,QAAM,cAAa,8CAAY,eAAZ,YAA0B;AAC7C,QAAM,UAAS,8CAAY,WAAZ,YAAsB;AAErC,QAAM,WAAO;AAAA,IACX,CAAU,MAAc,SAA8B;AACpD,UAAI,CAAC,cAAc,CAAC,QAAQ;AAC1B,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,YAAM,MAAM,MAAM,MAAM,GAAG,UAAU,OAAO,IAAI,IAAI;AAAA,QAClD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa;AAAA,QACf;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI;AAAA,UACR,aAAa,IAAI,YAAY,IAAI,MAAM,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,QACjE;AAAA,MACF;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA,IACA,CAAC,YAAY,MAAM;AAAA,EACrB;AAEA,QAAM,UAAM;AAAA,IACV,CAAU,SAA6B;AACrC,YAAM,MAAM,MAAM,MAAM,GAAG,UAAU,OAAO,IAAI,IAAI;AAAA,QAClD,QAAQ;AAAA,QACR,SAAS,EAAE,QAAQ,oBAAoB,aAAa,OAAO;AAAA,MAC7D,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,IAAI;AAAA,UACR,aAAa,IAAI,YAAY,IAAI,MAAM,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,QACjE;AAAA,MACF;AACA,aAAQ,MAAM,IAAI,KAAK;AAAA,IACzB;AAAA,IACA,CAAC,YAAY,MAAM;AAAA,EACrB;AAEA,QAAM,WAAO;AAAA,IACX,CACE,UAEA,KAAK,uBAAuB,KAAK;AAAA,IACnC,CAAC,IAAI;AAAA,EACP;AAEA,QAAM,eAAW;AAAA,IACf,CACE,UAEA,KAAK,2BAA2B,KAAK;AAAA,IACvC,CAAC,IAAI;AAAA,EACP;AAEA,QAAM,qBAAiB;AAAA,IACrB,CAAC,WACC;AAAA,MACE,8BAA8B,mBAAmB,MAAM,CAAC;AAAA,IAC1D;AAAA,IACF,CAAC,GAAG;AAAA,EACN;AAEA,QAAM,wBAAoB;AAAA,IACxB,CAAC,QAAgB,UACf;AAAA,MACE,8BAA8B,mBAAmB,MAAM,CAAC;AAAA,MACxD;AAAA,IACF;AAAA,IACF,CAAC,IAAI;AAAA,EACP;AAEA,SAAO,EAAE,MAAM,UAAU,gBAAgB,kBAAkB;AAC7D;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;;;AC7jBA,IAAAG,iBAAsC;AA+DtC,IAAM,+BAA2B,sCAQ/B,4BAA4B;AAM9B,IAAMC,QAAO;AAmBb,SAAS,wBACP,OACmC;AACnC,QAAM,cAAc,CAAC,EAAC,+BAAO;AAC7B,QAAM,kBAAkB,CAAC,GACvB,+BAAO,iBAAgB,MAAM,aAAa,SAAS;AAErD,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,SAAS;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;;;ACzHA,IAAAC,iBAAsC;AA2EtC,IAAM,wBAAoB,sCASxB,iBAAiB;AAMnB,IAAMC,QAAO;AAiBN,SAAS,iBACd,SACuC;AAxJzC;AAyJE,QAAM,iBAAgB,aAAQ,UAAR,YAAiB,IAAI,KAAK;AAChD,QAAM,UAAU,aAAa,WAAW;AAExC,QAAM,SAAS;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;;;ACpKA,IAAAC,iBAAsC;AACtC,IAAAC,gBAAwB;AAOxB,IAAM,0BAAsB,sCAI1B,mBAAmB;AAErB,IAAM,6BAAyB,sCAI7B,sBAAsB;AAExB,IAAMC,QAAO;AASN,SAAS,iBACd,KACyC;AACzC,QAAM,gBAAY,uBAAQ,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,SAAS;AAAA,IACb;AAAA,IACA,UAAUA,QAAO,EAAE,KAAK,UAAU;AAAA,EACpC;AAEA,QAAM,aAAS,uBAAQ,MAAM;AAC3B,QAAI,WAAW,QAAW;AACxB,aAAO;AAAA,IACT;AACA,WAAO,OAAO;AAAA,MACZ,OAAO,IAAI,CAAC,MAAM;AA7DxB;AA8DQ,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,uBAAQ,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,SAAS;AAAA,IACb;AAAA,IACA,UAAUA,QAAO,EAAE,aAAa,aAAa;AAAA,EAC/C;AAEA,QAAM,aAAS,uBAAQ,MAAM;AAC3B,QAAI,WAAW,QAAW;AACxB,aAAO;AAAA,IACT;AACA,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;;;ACzGA,IAAAC,iBAAsC;AAOtC,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,SAAS;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;;;AC/CA,IAAAC,gBAA4B;AAE5B,IAAAC,kBAAsC;AACtC,IAAAD,gBAA4B;AAM5B,IAAM,mCAA+B,uCAInC,4BAA4B;AAqB9B,SAAS,8BAGkB;AACzB,QAAM,aAAS;AAAA,IACb;AAAA,EACF;AAEA,aAAO;AAAA,IACL,CAAC,QAAgB,WACf,OAAO,EAAE,QAAQ,OAAO,CAAC;AAAA,IAC3B,CAAC,MAAM;AAAA,EACT;AACF;;;ACvCA,IAAAE,iBAAkE;AAsBlE,IAAM,2BAAuB;AAAA,EAC3B;AACF;AAoBA,SAAS,sBAAsB;AAAA,EAC7B,cAAc;AAAA,EACd,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,WAAW;AAAA,EACX;AAAA,EACA;AACF,GAA+B;AA9E/B;AAiFE,QAAM,kBACJ,+BACC,OAAO,YAAY,eAChB,aAAQ,QAAR,mBAAa,4BACb,WAHJ,YAIA;AACF,QAAM,YAAQ,wBAAmC,MAAM;AACrD,UAAM,UAAU,IAAI,QAAQ;AAAA,MAC1B,QAAQ;AAAA,MACR;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,gBAAgB,aAAa,QAAQ,eAAe,QAAQ,CAAC;AAEjE,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;;;AC7GA,IAAAC,kBAAsC;AAWtC,IAAM,iCAA6B,uCAIjC,0BAA0B;AAM5B,IAAMC,QAAO;AAUN,SAAS,gBACd,QACqC;AACrC,QAAM,OAAO,CAAC;AAEd,QAAM,SAAS;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;;;AC9CA,IAAAC,iBAA4C;AA2BrC,SAAS,YAAY,OAA4C;AA/CxE;AAgDE,QAAM,aAAa,mBAAmB;AACtC,QAAM,cAAa,iBAAM,eAAN,YAAoB,yCAAY,eAAhC,YAA8C;AACjE,QAAM,UAAS,iBAAM,WAAN,YAAgB,yCAAY,WAA5B,YAAsC;AAErD,QAAM,CAAC,QAAQ,SAAS,QAAI,yBAAsC,MAAM;AACxE,QAAM,CAAC,OAAO,QAAQ,QAAI,yBAAwB,IAAI;AAEtD,QAAM,iBAAa,uBAAsB,IAAI;AAE7C,QAAM,OAAO,MAAuD;AAClE,QAAI,CAAC,MAAM,QAAQ;AACjB,aAAO,EAAE,IAAI,OAAO,QAAQ,iBAAiB;AAAA,IAC/C;AACA,QAAI,CAAC,cAAc,CAAC,QAAQ;AAC1B,aAAO,EAAE,IAAI,OAAO,QAAQ,uBAAuB;AAAA,IACrD;AACA,cAAU,SAAS;AACnB,aAAS,IAAI;AACb,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,UAAU,mBAAmB;AAAA,QACtD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,aAAa;AAAA,QACf;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACnB,QAAQ,MAAM;AAAA,UACd,OAAO,MAAM;AAAA,UACb,MAAM,MAAM;AAAA,UACZ,WAAW,MAAM;AAAA,UACjB,UAAU,MAAM;AAAA,UAChB,UAAU,MAAM;AAAA,UAChB,sBAAsB,MAAM;AAAA,QAC9B,CAAC;AAAA,MACH,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAM,SAAS,eAAe,IAAI,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC;AAC/D,kBAAU,OAAO;AACjB,iBAAS,MAAM;AACf,eAAO,EAAE,IAAI,OAAO,OAAO;AAAA,MAC7B;AACA,gBAAU,QAAQ;AAClB,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,gBAAU,OAAO;AACjB,eAAS,OAAO;AAChB,aAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ;AAAA,IACtC;AAAA,EACF;AAEA,gCAAU,MAAM;AApGlB,QAAAC,KAAAC,KAAAC,KAAAC,KAAA;AAqGI,QAAI,CAAC,MAAM,QAAQ;AACjB;AAAA,IACF;AACA,UAAM,MAAM;AAAA;AAAA;AAAA;AAAA,MAIV;AAAA,MACA;AAAA,MACA,MAAM;AAAA,OACNH,MAAA,MAAM,UAAN,OAAAA,MAAe;AAAA,OACfC,MAAA,MAAM,SAAN,OAAAA,MAAc;AAAA,OACdC,MAAA,MAAM,cAAN,OAAAA,MAAmB;AAAA,OACnBC,MAAA,MAAM,aAAN,OAAAA,MAAkB;AAAA,OAClB,WAAM,aAAN,YAAkB;AAAA,MAClB,MAAM,yBAAyB,SAC3B,KACA,OAAO,MAAM,oBAAoB;AAAA,IACvC,EAAE,KAAK,GAAG;AACV,QAAI,QAAQ,WAAW,SAAS;AAC9B;AAAA,IACF;AACA,eAAW,UAAU;AACrB,SAAK,KAAK;AAAA,EAEZ,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,EACR,CAAC;AAED,SAAO,EAAE,QAAQ,OAAO,KAAK;AAC/B;;;AC9GA,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,SAAS;AACvB,eAAO;AAAA,MACT;AACA,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":["CONVEX_URLS","import_react_query","useTanstackQuery","useLiveQuery","import_server","import_server","import_react","SKIP","toResult","import_react","import_react_query","import_react","import_server","CONVEX_URLS","_a","_b","import_server","import_react","_a","_b","res","import_server","SKIP","import_server","SKIP","import_server","import_react","SKIP","import_server","SKIP","import_react","import_server","import_react","import_server","SKIP","import_react","_a","_b","_c","_d","import_react"]}
|
|
1
|
+
{"version":3,"sources":["../src/react.ts","../src/react/calls.ts","../src/react/offline/use-persistent-query.ts","../src/react/conversation-by-id.ts","../src/react/conversations.ts","../src/react/hooks.ts","../src/react/provider.ts","../src/auth-convex-client.ts","../src/resources/appointments.ts","../src/resources/rest-auth.ts","../src/resources/attachments.ts","../src/resources/conversations.ts","../src/resources/dialpad.ts","../src/resources/ehr.ts","../src/resources/notes.ts","../src/resources/notifications.ts","../src/resources/patient-details.ts","../src/resources/patients.ts","../src/resources/physicians.ts","../src/resources/reminders.ts","../src/resources/tasks.ts","../src/resources/translation.ts","../src/resources/user-settings.ts","../src/tracking/tracker.ts","../src/web-push.ts","../src/client.ts","../src/react/offline/envelope.ts","../src/react/offline/persister.ts","../src/react/offline/store.ts","../src/react/notifications.ts","../src/react/patient-family.ts","../src/react/patient-search.ts","../src/react/patients-bulk.ts","../src/react/reminders.ts","../src/react/tasks.ts","../src/react/tracking.ts","../src/react/user-settings.ts","../src/react/users.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// Conversation-by-id lookup\nexport { useConversationById } from \"./react/conversation-by-id\";\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 UseNotificationsActions,\n UseNotificationsOptions,\n UseNotificationsResult,\n} from \"./react/notifications\";\nexport {\n useNotifications,\n useNotificationsActions,\n} from \"./react/notifications\";\n// Offline reads (L2) — synchronous KV mirror seam. The RN app injects an\n// encrypted store; web omits it and gets a transparent NoopStore.\n// `readPersistedSavedAt` backs the offline/staleness indicator.\nexport { readPersistedSavedAt } from \"./react/offline/persister\";\nexport type { OfflineStore } from \"./react/offline/store\";\nexport { NoopStore } from \"./react/offline/store\";\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 {\n API_BASE_URLS,\n CONVEX_URLS,\n getTruthClient,\n resolveApiBaseUrl,\n resolveConvexUrl,\n TruthProvider,\n useOfflineEnabled,\n useOfflineStore,\n useTruthClient,\n useTruthSdkContext,\n} from \"./react/provider\";\n// Reminders\nexport { useRemindersForConversations } from \"./react/reminders\";\n// Conversation-task seen tracking\nexport { useConversationTaskMarkSeen } from \"./react/tasks\";\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// User sync (Clerk → Truth)\nexport type {\n UseUserSyncInput,\n UseUserSyncResult,\n} from \"./react/users\";\nexport { useUserSync } from \"./react/users\";\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\n// Active calls are inherently live — never mirrored (see useActiveCalls).\nimport { useQuery as useLiveQuery } from \"convex/react\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist: call history shown inside a conversation renders\n// from the mirror offline. Aliased so the call sites stay unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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 = useLiveQuery(listActiveRef, options ?? {});\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,\n skipped\n ? SKIP\n : {\n conversationId: conversationId as string,\n limit: options?.limit,\n },\n );\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,\n skipped ? SKIP : { callId: callId as string },\n );\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,\n skipped ? SKIP : { callId: callId as string, limit: options?.limit },\n );\n return toResult(result, skipped);\n}\n","/**\n * `usePersistentQuery` — the single seam that gives a Convex read hook a\n * durable, offline-readable mirror, while keeping the exact return\n * contract of `convex/react`'s `useQuery` (`data | undefined`).\n *\n * **Path A (TanStack, active).** The subscription is routed through\n * `@convex-dev/react-query`'s `convexQuery` + TanStack `useQuery`. The\n * Convex websocket still drives live updates (live always wins); the\n * difference is that the result now lives in the TanStack query cache,\n * which `<TruthProvider>` persists to the injected `OfflineStore` via\n * `PersistQueryClientProvider`. On cold start the persisted value\n * rehydrates and renders before the socket reconnects.\n *\n * **SKIP semantics are preserved.** `convexQuery(ref, \"skip\")` returns\n * `enabled: false`, so per the `@convex-dev/react-query` docs the query\n * is disabled — it shouldn't subscribe or issue a network request —\n * matching `useQuery(ref, \"skip\")` today. The call sites keep computing\n * `skipped` and passing it to `toResult`, so the\n * `{ data, loading, error }` contract is unchanged.\n *\n * **No mutation paths are touched.** This wraps reads only.\n *\n * @remarks The hand-rolled Path B implementation (raw `convex/react`\n * `useQuery` + `makeCacheKey`/`readEnvelope`/`writeEnvelope` against the\n * store) is a localized swap behind this same signature — see\n * `./cache-key` and `./envelope`.\n */\n\n\"use client\";\n\nimport { convexQuery } from \"@convex-dev/react-query\";\nimport { useQuery as useTanstackQuery } from \"@tanstack/react-query\";\nimport type { FunctionReference } from \"convex/server\";\n\n/** The Convex \"skip\" sentinel — matches the read hooks' `SKIP`. */\nexport type QueryArgs = Record<string, unknown> | \"skip\";\n\n/**\n * Drop-in replacement for `convex/react`'s `useQuery` that additionally\n * lets the result be mirrored offline. Returns the query data, or\n * `undefined` while loading / when skipped — exactly like the original.\n */\nexport function usePersistentQuery<Query extends FunctionReference<\"query\">>(\n ref: Query,\n args: Query[\"_args\"] | \"skip\",\n): Query[\"_returnType\"] | undefined {\n // `convexQuery` accepts the \"skip\" sentinel directly and returns\n // `enabled: false` for it, so we forward args verbatim — no branching\n // that could violate the rules of hooks. The inner casts keep\n // `convexQuery`'s arg type happy, while the outer generic mirrors\n // `convex/react`'s `useQuery` inference (args + return type) so an\n // aliased call site is a true drop-in with no extra annotations.\n const { data } = useTanstackQuery(\n convexQuery(\n ref as FunctionReference<\"query\">,\n args as Record<string, unknown> | \"skip\",\n ),\n );\n return data as Query[\"_returnType\"] | undefined;\n}\n","/**\n * React hook for a single conversation lookup by Convex id — Truth SDK.\n *\n * Replaces CommHub's `useGet_Conversation_EventQuery` (backed by the\n * Hasura `Get_Conversation_Event` GraphQL query). Queries the Convex\n * `conversations:getById` function which returns the raw conversation\n * row, or `null` when the id doesn't exist.\n *\n * The returned `ConversationRow` carries the same phone-pair fields as\n * `useConversationByPhonePair` — `patientPhone`, `providerPhone`,\n * `phonePair`, `patientId` — but is keyed on the Convex document `_id`\n * rather than the pair. Use this when you have a Convex id from a push\n * notification or a deep-link route param.\n *\n * Must be used within a `<TruthProvider />`.\n *\n * @example\n * ```tsx\n * import { useConversationById } from '@hipnation-truth/sdk/react/conversation-by-id';\n *\n * function ConversationScreen({ id }: { id: string }) {\n * const { data: conversation, loading } = useConversationById(id);\n * if (loading) return <Spinner />;\n * if (!conversation) return <NotFound />;\n * return <View>{conversation.patientPhone}</View>;\n * }\n * ```\n */\n\n\"use client\";\n\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { ConversationRow, UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist (Phase 2.1): a deep-linked conversation should\n// open from the mirror. Aliased so the call site below is unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\n\n// ---------------------------------------------------------------------------\n// Convex function reference\n// ---------------------------------------------------------------------------\n\nconst conversationsGetByIdRef = makeFunctionReference<\n \"query\",\n { id: string },\n ConversationRow | null\n>(\"conversations:getById\");\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to a single conversation by its Convex document id. Returns\n * `null` when no conversation exists for that id.\n *\n * `data` is `undefined` while loading or when `id` is falsy (skip). Pass\n * `null` or `undefined` to skip the query — useful when the route param\n * hasn't resolved yet.\n */\nfunction useConversationById(\n id: string | null | undefined,\n): UseQueryResult<ConversationRow | null> {\n const skipped = !id;\n const result = useQuery(\n conversationsGetByIdRef as FunctionReference<\"query\">,\n skipped ? \"skip\" : { id: id as string },\n ) as ConversationRow | null | undefined;\n\n if (skipped) {\n return { data: undefined, loading: false, error: undefined };\n }\n return {\n data: result,\n loading: result === undefined,\n error: undefined,\n };\n}\n\nexport { useConversationById };\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 type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport { useMemo } from \"react\";\n// Offline-reads allowlist: all conversation read hooks (list, search, by-pair,\n// messages, notes, tasks, unread counts/aggregate) route through the persisted\n// mirror so previously-loaded threads render offline. The wrapper preserves the\n// `data | undefined` contract, so each call site uses `usePersistentQuery` in\n// place of `convex/react`'s `useQuery`.\nimport { usePersistentQuery } from \"./offline/use-persistent-query\";\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 /**\n * Denormalized preview of the most recent message, for the inbox list +\n * right-panel snippet (so the UI shows the message instead of a literal\n * \"Conversation\"). Populated by the write path + a one-off backfill.\n */\n lastMessageText: string | null;\n /** `\"sms\" | \"call\" | \"voicemail\"` — drives the preview's icon/label. */\n lastMessageKind: string | null;\n /** `\"inbound\" | \"outbound\"` of the previewed message. */\n lastDirection: string | null;\n /**\n * Call lifecycle state when the previewed event is a call/voicemail:\n * `\"missed\" | \"connected\" | \"ringing\" | \"hangup\" | \"voicemail\" |\n * \"voicemail_uploaded\"`. Lets the inbox show \"Missed call\" / \"Call in\n * progress\" / \"Voicemail\" instead of a bare \"Call\". Null for SMS.\n */\n lastCallState: string | null;\n /** Call duration in seconds (calls only). */\n lastCallDurationSec: number | null;\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 /**\n * Task title — only standalone (New-Task-modal) tasks have one; conversation\n * tasks have none (the `description` is the content). When set, the My Tasks\n * card shows it as the headline with the real `description` below it.\n */\n title: string | null;\n /** Normalized `(patient_phone | provider_phone)` for the task's conversation. */\n phonePair: string | null;\n /**\n * Resolved patient display name. `null` when the conversation has no\n * linked patient — callers fall back to formatting `phonePair`.\n */\n patientName: string | null;\n /**\n * Set of user ids who have seen this task (opened the detail panel).\n * Replaces the Hasura `event_activities.seen` boolean — use\n * `seenBy.includes(userId)` to derive the per-user boolean.\n */\n seenBy: string[];\n /**\n * Which backend table the task lives in:\n * - `\"conversation\"` — `conversationTasks`, tied to a conversation.\n * - `\"standalone\"` — the general `tasks` table (New Task modal with\n * a patient but no conversation). For standalone rows\n * `conversationId` is `\"\"` and `phonePair` is `null`; status /\n * priority mutations must route to the `tasks` resource.\n */\n source: \"conversation\" | \"standalone\";\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 /** Voicemail/call transcript — Dialpad delivers `{ text }` (B5). */\n transcript: unknown | null;\n /** AI-generated Dialpad recap summary (calls only; null for SMS). */\n recapSummary: string | null;\n text: string | null;\n mms: boolean;\n mmsUrl: string | null;\n messageStatus: string | null;\n /**\n * True while an outbound SMS is the optimistic local row inserted by the\n * Truth `sendMessage` path and not yet reconciled by the Dialpad webhook\n * (Truth #100). Cleared once the real event lands. Always `false` for calls\n * and for reconciled rows. Clients should gate a \"sending\" affordance on\n * THIS flag — not on `messageStatus === \"optimistic\"`, a value that is\n * minted nowhere in the pipeline.\n */\n optimistic: boolean;\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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?.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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 = usePersistentQuery(\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 { useEffect } from \"react\";\n// Offline-reads allowlist: patient / appointment / physician / pharmacy /\n// photo / message / medical-record reads render from the mirror offline.\n// Aliased so the call sites stay unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\nimport { useTruthSdkContext } from \"./provider\";\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 *\n * OFFLINE-READS POLICY: these medical-record queries ARE mirrored offline\n * via `usePersistentQuery`, so medications, problems, allergies and\n * appointments render from the at-rest mirror when the device is offline.\n * Full medical records are the most sensitive PHI we hold, so they are\n * only ever written to the encrypted-at-rest store (256-bit MMKV key held\n * in the device keychain), never to any plaintext storage. Keep them on\n * the encrypted mirror only — do not relax the store's encryption.\n */\nfunction usePatientMedical(\n elationId: number | undefined,\n options?: UsePatientMedicalOptions,\n) {\n const sdkContext = useTruthSdkContext();\n const apiBaseUrl = options?.apiBaseUrl ?? sdkContext?.apiBaseUrl;\n const apiKey = options?.apiKey ?? sdkContext?.apiKey;\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 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, apiBaseUrl, 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 sdkContext = useTruthSdkContext();\n const apiBaseUrl = options?.apiBaseUrl ?? sdkContext?.apiBaseUrl;\n const apiKey = options?.apiKey ?? sdkContext?.apiKey;\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 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 }, [input.hintId, input.elationId, apiBaseUrl, apiKey, options?.skipRefresh]);\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 sdkContext = useTruthSdkContext();\n const apiBaseUrl = options?.apiBaseUrl ?? sdkContext?.apiBaseUrl;\n const apiKey = options?.apiKey ?? sdkContext?.apiKey;\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 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, apiBaseUrl, 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 /** Voicemail/call transcript — Dialpad delivers `{ text }` (B5). */\n transcript: unknown | null;\n /** AI-generated Dialpad recap summary (calls only; null for SMS). */\n recapSummary: string | null;\n text: string | null;\n mms: boolean;\n mmsUrl: string | null;\n messageStatus: string | null;\n /**\n * True while an outbound SMS is the optimistic local row inserted by the\n * Truth `sendMessage` path and not yet reconciled by the Dialpad webhook\n * (Truth #100). Clients gate a \"sending\" affordance on THIS flag — not on\n * `messageStatus === \"optimistic\"`, a value minted nowhere. Always `false`\n * for calls and reconciled rows.\n */\n optimistic: boolean;\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 * TruthProvider — single mount point for the Truth SDK on the frontend.\n *\n * - Connects to the correct Convex deployment for the given environment.\n * - Resolves the Truth REST API base URL + API key (env-var fallbacks\n * when no prop is passed) and exposes them via React context.\n * - Instantiates a shared `TruthClient` and exposes it via\n * `useTruthClient()` for React code and `getTruthClient()` for\n * non-React helpers (event handlers, lib modules).\n *\n * @example\n * ```tsx\n * import { TruthProvider } from '@hipnation-truth/sdk/react';\n *\n * function App() {\n * // Reads EXPO_PUBLIC_TRUTH_API_KEY automatically.\n * return (\n * <TruthProvider environment=\"uat\">\n * <MyApp />\n * </TruthProvider>\n * );\n * }\n * ```\n */\n\n\"use client\";\n\nimport { ConvexQueryClient } from \"@convex-dev/react-query\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { PersistQueryClientProvider } from \"@tanstack/react-query-persist-client\";\nimport { ConvexProvider, ConvexReactClient } from \"convex/react\";\nimport type { ReactNode } from \"react\";\nimport {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from \"react\";\nimport { TruthClient } from \"../client\";\nimport type { AuthTokenFetcher, Environment } from \"../types/config\";\nimport { SCHEMA_VERSION } from \"./offline/envelope\";\nimport { createOfflinePersister } from \"./offline/persister\";\nimport type { OfflineStore } from \"./offline/store\";\nimport { isNoopStore, NOOP_STORE } from \"./offline/store\";\n\n/**\n * How long a persisted query may rehydrate the UI before it's treated as\n * too stale to restore. Also used as the TanStack `gcTime` so inactive\n * queries survive in memory long enough to be persisted. 7 days balances\n * \"useful offline mirror\" against \"don't show month-old PHI\".\n */\nconst OFFLINE_MAX_AGE_MS = 1000 * 60 * 60 * 24 * 7;\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// Truth REST API base URL for each environment. Used by the\n// HTTP-backed SDK surfaces (device registration → Pinpoint, etc.)\n// which can't go through Convex directly because the underlying\n// AWS SDK calls live on the Lambda. Mirrors the Convex map: UAT +\n// production both hit the prod Lambda; everything else hits the\n// sandbox Lambda.\nexport const API_BASE_URLS: Record<string, string> = {\n local: \"https://app.sandbox.communication-hub.com\",\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 * 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\n/**\n * Resolve the Truth REST API base URL for a given environment. Mirrors\n * `resolveConvexUrl`. Honors an explicit override and falls back to\n * sandbox for unknown environments.\n */\nexport function resolveApiBaseUrl(\n environment: string | undefined,\n override?: string,\n): string {\n if (override) {\n return override;\n }\n const env = environment ?? \"sandbox\";\n return API_BASE_URLS[env] ?? API_BASE_URLS.sandbox;\n}\n\n/**\n * Read the env-var fallback for a value (e.g. `EXPO_PUBLIC_TRUTH_API_KEY`).\n * Cross-runtime safe — falls back to undefined when `process.env` is\n * unavailable (e.g. Vite without dotenv shim).\n */\nfunction readEnv(name: string): string | undefined {\n if (typeof process === \"undefined\" || !process.env) {\n return undefined;\n }\n const v = (process.env as Record<string, string | undefined>)[name];\n return typeof v === \"string\" && v.length > 0 ? v : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Context — exposes the Truth REST API base URL + API key + a shared\n// TruthClient to nested hooks. `useNotifications` (and any future\n// HTTP-backed hooks) read from here so CommHub doesn't have to pass\n// apiKey / apiBaseUrl to every hook call.\n// ---------------------------------------------------------------------------\n\ninterface TruthSdkContextValue {\n apiBaseUrl: string;\n apiKey: string;\n environment: string;\n client: TruthClient;\n /** Injected offline mirror (or the Noop default on web / flag off). */\n offlineStore: OfflineStore;\n /** Whether the offline-reads layer is active for this provider. */\n offlineEnabled: boolean;\n}\n\nconst TruthSdkContext = createContext<TruthSdkContextValue | null>(null);\n\n/**\n * Read the Truth REST API base URL + API key + shared client from\n * `TruthProvider`. Returns `null` when the provider isn't mounted,\n * which lets hooks fall back to explicit options for backwards\n * compatibility.\n */\nexport function useTruthSdkContext(): TruthSdkContextValue | null {\n return useContext(TruthSdkContext);\n}\n\n/**\n * Read the injected offline mirror. Returns the shared `NoopStore`\n * (transparent pass-through) when no provider is mounted or no store was\n * injected — so callers never have to null-check.\n */\nexport function useOfflineStore(): OfflineStore {\n return useContext(TruthSdkContext)?.offlineStore ?? NOOP_STORE;\n}\n\n/**\n * Whether the offline-reads layer is active. `false` when no provider is\n * mounted or the feature flag is off.\n */\nexport function useOfflineEnabled(): boolean {\n return useContext(TruthSdkContext)?.offlineEnabled ?? false;\n}\n\n/**\n * Hook variant of `getTruthClient()` for code that lives inside the\n * React tree. Returns the shared `TruthClient` instance from\n * `TruthProvider`. Throws if the provider isn't mounted.\n */\nexport function useTruthClient(): TruthClient {\n const ctx = useContext(TruthSdkContext);\n if (!ctx) {\n throw new Error(\n \"useTruthClient() called outside <TruthProvider>. Wrap your app in <TruthProvider> from @hipnation-truth/sdk/react.\",\n );\n }\n return ctx.client;\n}\n\n// ---------------------------------------------------------------------------\n// Singleton bridge for non-React helpers (lib/ modules, event-handler\n// closures). `TruthProvider` populates this on mount so callers can\n// `import { getTruthClient } from '@hipnation-truth/sdk/react'` without\n// needing to be inside the component tree.\n// ---------------------------------------------------------------------------\n\nlet _activeClient: TruthClient | null = null;\n\n/**\n * Return the shared `TruthClient` instance set by `<TruthProvider>`.\n * Throws if `TruthProvider` hasn't mounted yet — call this only from\n * code that runs after the provider has rendered (event handlers,\n * effects, async helpers invoked from rendered components).\n */\nexport function getTruthClient(): TruthClient {\n if (!_activeClient) {\n throw new Error(\n \"getTruthClient() called before <TruthProvider> mounted. Either wrap your app in <TruthProvider> or call useTruthClient() inside a component.\",\n );\n }\n return _activeClient;\n}\n\ninterface TruthProviderProps {\n /** Truth environment — determines which Convex + Lambda deployment to use */\n environment?: string;\n /** Override the Convex URL directly */\n convexUrl?: string;\n /**\n * Override the Truth REST API base URL directly. Optional — defaults\n * to the environment-mapped URL (`API_BASE_URLS[environment]`), then\n * falls back to `process.env.EXPO_PUBLIC_TRUTH_API_BASE_URL` if set.\n */\n apiBaseUrl?: string;\n /**\n * `hn_live_*` API key for the caller's application. Optional —\n * defaults to `process.env.EXPO_PUBLIC_TRUTH_API_KEY`. Stored in\n * React context + on the shared TruthClient, never logged.\n */\n apiKey?: string;\n /**\n * Optional source / tenant metadata for event tracking. Mirrors\n * `TruthClient` constructor options.\n */\n source?: string;\n sourceVersion?: string;\n tenantId?: string;\n /**\n * Per-call Clerk JWT fetcher (template \"convex\") identifying the\n * signed-in user to Convex. Wire it to your identity provider, e.g.\n * `useAuth().getToken({ template: \"convex\" })` from `@clerk/expo`.\n * Applied to both the live websocket client (React hooks) and the\n * shared `TruthClient` (resource methods). Identity changes are\n * picked up without remounting — the latest fetcher is read through\n * a ref on every call.\n */\n getAuthToken?: AuthTokenFetcher;\n /**\n * Synchronous encrypted KV mirror for durable offline reads, injected\n * by the consuming app (e.g. MMKV on `ch/`). Omit on web — the SDK\n * falls back to a `NoopStore` and the offline layer is a transparent\n * pass-through. Never import a native module into the SDK; inject it\n * here instead.\n */\n offlineStore?: OfflineStore;\n /**\n * Feature flag for the offline-reads layer. Default `false`. Has no\n * effect unless a real (non-Noop) `offlineStore` is also injected.\n * When both are set, the TanStack query cache is persisted to the\n * store so last-known reads survive a cold start with no network.\n */\n offlineEnabled?: boolean;\n children: ReactNode;\n}\n\nfunction TruthProvider({\n environment = \"sandbox\",\n convexUrl,\n apiBaseUrl,\n apiKey,\n source,\n sourceVersion,\n tenantId,\n getAuthToken,\n offlineStore = NOOP_STORE,\n offlineEnabled = false,\n children,\n}: TruthProviderProps) {\n const url = resolveConvexUrl(environment, convexUrl);\n // Resolve apiBaseUrl with this precedence: explicit prop →\n // EXPO_PUBLIC_TRUTH_API_BASE_URL env var → environment map.\n const resolvedApiBaseUrl =\n apiBaseUrl ??\n readEnv(\"EXPO_PUBLIC_TRUTH_API_BASE_URL\") ??\n resolveApiBaseUrl(environment);\n // Resolve apiKey with this precedence: explicit prop →\n // EXPO_PUBLIC_TRUTH_API_KEY env var.\n const resolvedApiKey = apiKey ?? readEnv(\"EXPO_PUBLIC_TRUTH_API_KEY\") ?? \"\";\n\n const convexClient = useMemo(() => new ConvexReactClient(url), [url]);\n\n // --- Caller identity (Clerk JWT) ------------------------------------\n //\n // Consumers typically pass an inline arrow for `getAuthToken`, whose\n // identity changes every render. Read it through a ref behind a\n // stable wrapper so the TruthClient + websocket auth don't churn.\n const getAuthTokenRef = useRef<AuthTokenFetcher | undefined>(getAuthToken);\n getAuthTokenRef.current = getAuthToken;\n const hasAuthFetcher = Boolean(getAuthToken);\n const stableGetAuthToken = useMemo<AuthTokenFetcher | undefined>(\n () =>\n hasAuthFetcher\n ? async () => (await getAuthTokenRef.current?.()) ?? null\n : undefined,\n [hasAuthFetcher],\n );\n\n // Authenticate the live websocket connection. `setAuth` re-invokes\n // the fetcher whenever the server reports an expired token, so the\n // 60s Clerk template lifetime never interrupts subscriptions.\n useEffect(() => {\n if (stableGetAuthToken) {\n convexClient.setAuth(async () => (await stableGetAuthToken()) ?? null);\n } else {\n convexClient.clearAuth();\n }\n }, [convexClient, stableGetAuthToken]);\n\n // --- Offline reads (Path A) ----------------------------------------\n //\n // Route Convex query subscriptions through TanStack Query so the\n // result set lives in a cache we can persist. The websocket still\n // drives live updates (live always wins); persistence only adds a\n // durable mirror. `usePersistentQuery` reads from this client.\n //\n // `gcTime` is multi-day so recently-used queries survive while\n // inactive long enough to be persisted/restored across a cold start.\n // The Convex integration manages `staleTime` per query, so we don't\n // set it globally.\n const convexQueryClient = useMemo(\n () => new ConvexQueryClient(convexClient),\n [convexClient],\n );\n const queryClient = useMemo(() => {\n return new QueryClient({\n defaultOptions: {\n queries: {\n queryKeyHashFn: convexQueryClient.hashFn(),\n queryFn: convexQueryClient.queryFn(),\n gcTime: OFFLINE_MAX_AGE_MS,\n // Convex throws on subscribe rather than returning errors;\n // retrying a permanently-failing query buys nothing.\n retry: false,\n },\n },\n });\n }, [convexQueryClient]);\n\n // `ConvexQueryClient.connect` must run exactly once per client — it\n // throws if called twice, and calling it as a side effect inside the\n // `useMemo` above risked re-running whenever React discarded the\n // memoized value. Wire it here, tracking the connected client by\n // identity so a new `convexQueryClient` (e.g. after a `url` change)\n // connects once while re-renders with the same instance are no-ops.\n const connectedClientRef = useRef<ConvexQueryClient | null>(null);\n useEffect(() => {\n if (connectedClientRef.current !== convexQueryClient) {\n convexQueryClient.connect(queryClient);\n connectedClientRef.current = convexQueryClient;\n }\n }, [convexQueryClient, queryClient]);\n\n // Persist only when the app both injected a real store AND opted in.\n // On web (Noop store) or with the flag off this stays undefined and we\n // mount a plain QueryClientProvider — no persistence, behavior intact.\n const persister = useMemo(\n () =>\n offlineEnabled && !isNoopStore(offlineStore)\n ? createOfflinePersister(offlineStore)\n : undefined,\n [offlineEnabled, offlineStore],\n );\n\n const truthClient = useMemo(\n () =>\n new TruthClient({\n // Pin the resource client to the SAME deployment the React hooks\n // use; without this it falls back to CONVEX_URLS[environment] and a\n // caller-supplied `convexUrl` would split hooks vs. client methods.\n convexUrl: url,\n apiKey: resolvedApiKey,\n environment: environment as Environment,\n apiBaseUrl: resolvedApiBaseUrl,\n source: source ?? \"unknown\",\n sourceVersion: sourceVersion ?? \"unknown\",\n tenantId: tenantId ?? \"\",\n autoInitServiceWorker: false,\n getAuthToken: stableGetAuthToken,\n }),\n [\n url,\n resolvedApiKey,\n resolvedApiBaseUrl,\n environment,\n source,\n sourceVersion,\n tenantId,\n stableGetAuthToken,\n ],\n );\n\n // Populate the non-React singleton bridge so helpers that import\n // `getTruthClient` from the SDK directly resolve to the same\n // instance the React tree is using.\n useEffect(() => {\n _activeClient = truthClient;\n return () => {\n if (_activeClient === truthClient) {\n _activeClient = null;\n }\n // Tear down the prior client's tracker lifecycle so a config change /\n // remount doesn't leak duplicate background event delivery.\n void truthClient.destroy().catch(() => {\n /* best-effort cleanup */\n });\n };\n }, [truthClient]);\n\n const sdkContext = useMemo<TruthSdkContextValue>(\n () => ({\n apiBaseUrl: resolvedApiBaseUrl,\n apiKey: resolvedApiKey,\n environment,\n client: truthClient,\n offlineStore,\n offlineEnabled,\n }),\n [\n resolvedApiBaseUrl,\n resolvedApiKey,\n environment,\n truthClient,\n offlineStore,\n offlineEnabled,\n ],\n );\n\n // `ConvexProvider` stays innermost so `convex/react` hooks (mutations,\n // any non-wrapped reads) keep working. The TanStack provider wraps it:\n // `PersistQueryClientProvider` when a persister exists, otherwise a\n // plain `QueryClientProvider`.\n const convexTree = createElement(\n ConvexProvider,\n { client: convexClient },\n children,\n );\n const queryTree = persister\n ? createElement(\n PersistQueryClientProvider,\n {\n client: queryClient,\n persistOptions: {\n persister,\n maxAge: OFFLINE_MAX_AGE_MS,\n // A SCHEMA_VERSION bump busts the entire dehydrated cache, so\n // an older build's shapes can never rehydrate the new UI.\n buster: String(SCHEMA_VERSION),\n },\n },\n convexTree,\n )\n : createElement(QueryClientProvider, { client: queryClient }, convexTree);\n\n return createElement(\n TruthSdkContext.Provider,\n { value: sdkContext },\n queryTree,\n );\n}\n\nexport { TruthProvider };\nexport type { TruthProviderProps };\n","/**\n * AuthAwareConvexHttpClient — ConvexHttpClient that refreshes the\n * caller's identity token before every call.\n *\n * `ConvexHttpClient.setAuth()` takes a static JWT, but Clerk's \"convex\"\n * template tokens are short-lived (60s). This subclass asks the\n * injected {@link AuthTokenFetcher} for a current token immediately\n * before each query/mutation/action — identity-provider SDKs (Clerk)\n * cache tokens internally and refresh on expiry, so this is cheap.\n *\n * With no fetcher configured it behaves exactly like the base client.\n */\nimport { ConvexHttpClient, type HttpMutationOptions } from \"convex/browser\";\nimport type {\n ArgsAndOptions,\n FunctionReference,\n FunctionReturnType,\n OptionalRestArgs,\n} from \"convex/server\";\nimport type { AuthTokenFetcher } from \"./types/config\";\n\nexport class AuthAwareConvexHttpClient extends ConvexHttpClient {\n private readonly getAuthToken?: AuthTokenFetcher;\n\n constructor(address: string, getAuthToken?: AuthTokenFetcher) {\n super(address);\n this.getAuthToken = getAuthToken;\n }\n\n /**\n * Pull a fresh token from the fetcher and apply it. A fetcher error\n * keeps the previously-applied token rather than dropping auth\n * mid-session.\n */\n private async syncAuth(): Promise<void> {\n if (!this.getAuthToken) {\n return;\n }\n try {\n const token = await this.getAuthToken();\n if (token) {\n this.setAuth(token);\n } else {\n this.clearAuth();\n }\n } catch {\n /* keep last known token */\n }\n }\n\n override async query<Query extends FunctionReference<\"query\">>(\n query: Query,\n ...args: OptionalRestArgs<Query>\n ): Promise<FunctionReturnType<Query>> {\n await this.syncAuth();\n return super.query(query, ...args);\n }\n\n override async mutation<Mutation extends FunctionReference<\"mutation\">>(\n mutation: Mutation,\n ...args: ArgsAndOptions<Mutation, HttpMutationOptions>\n ): Promise<FunctionReturnType<Mutation>> {\n await this.syncAuth();\n return super.mutation(mutation, ...args);\n }\n\n override async action<Action extends FunctionReference<\"action\">>(\n action: Action,\n ...args: OptionalRestArgs<Action>\n ): Promise<FunctionReturnType<Action>> {\n await this.syncAuth();\n return super.action(action, ...args);\n }\n}\n","/**\n * AppointmentResource provides data access to normalized appointment records\n * backed by Convex.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type { Appointment, AppointmentListOptions } from \"../types/appointment\";\nimport type { PaginatedResult } from \"../types/config\";\n\nclass AppointmentResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convexClient: ConvexHttpClient) {\n this.convex = convexClient;\n }\n\n /**\n * Get an appointment by its Truth platform ID.\n */\n async get(id: string): Promise<Appointment | null> {\n try {\n const result = await this.convex.query(\n \"appointments:getById\" as never,\n { id } as never,\n );\n return (result as Appointment) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * List appointments with optional filters, pagination, and limit.\n */\n async list(\n options?: AppointmentListOptions,\n ): Promise<PaginatedResult<Appointment>> {\n try {\n const result = await this.convex.query(\n \"appointments:list\" as never,\n {\n patientId: options?.patientId,\n startDate: options?.startDate,\n endDate: options?.endDate,\n status: options?.status,\n limit: options?.limit,\n cursor: options?.cursor,\n } as never,\n );\n\n const typed = result as PaginatedResult<Appointment> | undefined;\n\n return typed ?? { data: [], cursor: null, hasMore: false };\n } catch {\n return { data: [], cursor: null, hasMore: false };\n }\n }\n}\n\nexport { AppointmentResource };\n","/**\n * Shared auth-header builder for the HTTP-backed SDK resources.\n *\n * Every Truth REST/RPC call carries `X-API-Key`. When the host app wired\n * a `getAuthToken` (Clerk JWT fetcher), the caller's bearer token rides\n * alongside it — required once the application uses a publishable key\n * (`hn_pk_*`), which the API gate accepts only with a verified user JWT.\n */\nimport type { AuthTokenFetcher } from \"../types/config\";\n\nexport interface RestAuth {\n apiKey: string;\n getAuthToken?: AuthTokenFetcher;\n}\n\n/**\n * Build request headers: `X-API-Key` always, `Authorization: Bearer\n * <jwt>` when a token is available, and `Content-Type: application/json`\n * when `json` is set. A token-fetcher error degrades to key-only rather\n * than failing the request.\n */\nexport async function buildAuthHeaders(\n auth: RestAuth,\n options?: { json?: boolean; accept?: boolean },\n): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n \"X-API-Key\": auth.apiKey,\n };\n if (options?.accept !== false) {\n headers.Accept = \"application/json\";\n }\n if (options?.json) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n if (auth.getAuthToken) {\n try {\n const token = await auth.getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n } catch {\n /* key-only fallback */\n }\n }\n return headers;\n}\n","/**\n * AttachmentsResource — presigned S3 upload/download + Convex metadata.\n *\n * Replaces CommHub's base64-in-Postgres attachment flow:\n * 1. client.attachments.createUploadUrl(...) → presigned PUT URL\n * 2. Client PUTs bytes directly to S3\n * 3. client.attachments.record(...) → writes Convex metadata\n * 4. client.attachments.getDownloadUrl(s3Key) → presigned GET URL\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type { AuthTokenFetcher } from \"../types/config\";\nimport { buildAuthHeaders, type RestAuth } from \"./rest-auth\";\n\nexport interface CreateUploadUrlInput {\n fileName: string;\n mimeType: string;\n size: number;\n conversationId?: string;\n}\n\nexport interface CreateUploadUrlResult {\n uploadUrl: string;\n s3Key: string;\n expiresIn: number;\n applicationId: string | null;\n}\n\nexport interface GetDownloadUrlResult {\n url: string;\n expiresIn: number;\n}\n\nexport interface RecordAttachmentInput {\n s3Key: string;\n fileName: string;\n mimeType: string;\n size: number;\n conversationId?: string;\n uploadedBy: string;\n}\n\nexport interface Attachment {\n _id: string;\n s3Key: string;\n fileName: string;\n mimeType: string;\n size: number;\n conversationId?: string;\n uploadedBy: string;\n createdAt: string;\n}\n\nclass AttachmentsError extends Error {\n readonly status: number;\n\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Attachment ${operation} failed (HTTP ${status})`);\n this.name = \"AttachmentsError\";\n this.status = status;\n }\n}\n\nclass AttachmentsResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly auth: RestAuth;\n private readonly convex: ConvexHttpClient;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n convexClient: ConvexHttpClient,\n getAuthToken?: AuthTokenFetcher,\n ) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n this.auth = { apiKey, getAuthToken };\n this.convex = convexClient;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"POST\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new AttachmentsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n async createUploadUrl(\n input: CreateUploadUrlInput,\n ): Promise<CreateUploadUrlResult> {\n return await this.post<CreateUploadUrlResult>(\n \"/attachments/upload-url\",\n input,\n );\n }\n\n async getDownloadUrl(\n s3Key: string,\n expiresIn?: number,\n ): Promise<GetDownloadUrlResult> {\n return await this.post<GetDownloadUrlResult>(\"/attachments/download-url\", {\n s3Key,\n expiresIn,\n });\n }\n\n async record(\n input: RecordAttachmentInput,\n ): Promise<{ attachmentId: string; s3Key: string }> {\n return (await this.convex.mutation(\n \"attachments:record\" as never,\n input as never,\n )) as { attachmentId: string; s3Key: string };\n }\n\n async get(attachmentId: string): Promise<Attachment | null> {\n try {\n const row = (await this.convex.query(\n \"attachments:getById\" as never,\n { attachmentId } as never,\n )) as Attachment | null;\n return row ?? null;\n } catch {\n return null;\n }\n }\n\n async listByConversation(conversationId: string): Promise<Attachment[]> {\n try {\n const rows = (await this.convex.query(\n \"attachments:listByConversation\" as never,\n { conversationId } as never,\n )) as Attachment[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n\n /**\n * One-shot upload: presign → PUT to S3 → record in Convex → return a\n * 7-day signed download URL ready to embed in an outbound SMS. Caller\n * passes the resulting `downloadUrl` to `messages.dialpad.sendSms` (or\n * the new `messages.sendAttachmentMessage`) to actually deliver it.\n *\n * Replaces CommHub's NestJS `/send-attachment` controller in a single\n * SDK call — no base64 round-trip, no legacy `/attachments/:id/download`\n * REST endpoint required.\n */\n async upload(input: {\n file: Blob | ArrayBuffer | Uint8Array;\n fileName: string;\n mimeType: string;\n size: number;\n conversationId?: string;\n uploadedBy: string;\n /** Download URL TTL in seconds. Default 7 days. */\n downloadExpiresIn?: number;\n }): Promise<{\n attachmentId: string;\n s3Key: string;\n downloadUrl: string;\n }> {\n const presigned = await this.createUploadUrl({\n fileName: input.fileName,\n mimeType: input.mimeType,\n size: input.size,\n conversationId: input.conversationId,\n });\n\n // Cast through `BodyInit` — Uint8Array is accepted by undici/fetch\n // at runtime but the lib.dom types only declare BufferSource so TS\n // doesn't see the overload. Same trick the AttachmentsResource\n // already uses elsewhere.\n const body: BodyInit =\n input.file instanceof Blob\n ? input.file\n : input.file instanceof Uint8Array\n ? (input.file as unknown as BodyInit)\n : (new Uint8Array(input.file) as unknown as BodyInit);\n\n // 30s timeout matches the legacy NestJS handler so a stuck S3 PUT\n // can't hang the calling UI thread indefinitely.\n const abort = new AbortController();\n const timer = setTimeout(() => abort.abort(), 30_000);\n let putRes: Response;\n try {\n putRes = await fetch(presigned.uploadUrl, {\n method: \"PUT\",\n headers: { \"Content-Type\": input.mimeType },\n body,\n signal: abort.signal,\n });\n } catch (err) {\n // AbortController fires DOMException(\"AbortError\"); rewrap as a\n // typed AttachmentsError(0, …) so callers' `instanceof\n // AttachmentsError` checks catch the timeout consistently with\n // every other failure mode here.\n const isAbort =\n err instanceof Error &&\n (err.name === \"AbortError\" || err.name === \"TimeoutError\");\n if (isAbort) {\n throw new AttachmentsError(\n \"s3-put\",\n 0,\n \"S3 upload timed out after 30s\",\n );\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n if (!putRes.ok) {\n throw new AttachmentsError(\n \"s3-put\",\n putRes.status,\n `S3 PUT ${putRes.status} for ${presigned.s3Key}`,\n );\n }\n\n const recorded = await this.record({\n s3Key: presigned.s3Key,\n fileName: input.fileName,\n mimeType: input.mimeType,\n size: input.size,\n conversationId: input.conversationId,\n uploadedBy: input.uploadedBy,\n });\n\n const signed = await this.getDownloadUrl(\n presigned.s3Key,\n input.downloadExpiresIn ?? 7 * 24 * 3600,\n );\n\n return {\n attachmentId: recorded.attachmentId,\n s3Key: presigned.s3Key,\n downloadUrl: signed.url,\n };\n }\n}\n\nexport { AttachmentsResource, AttachmentsError };\n","/**\n * ConversationsResource — write methods that hang off a specific\n * conversation: notes, tasks, outbound messages.\n *\n * Replaces CommHub's `truthConversationApi.ts` raw-fetch wrappers so\n * the frontend goes through a typed SDK surface instead of building\n * URLs by hand.\n *\n * All methods proxy the matching oRPC procedures (`/conversations/*`)\n * via Truth's application-key auth. Errors surface as\n * `ConversationsError` with a status code so callers can distinguish\n * transport failures (status=0) from API rejections (status>=400).\n */\n\nimport type { AuthTokenFetcher } from \"../types/config\";\nimport { buildAuthHeaders, type RestAuth } from \"./rest-auth\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Address a conversation by either Convex `_id` or the phonePair. */\nexport interface ConversationAddress {\n conversationId?: string;\n phonePair?: string;\n}\n\nexport type ConversationTaskStatus = \"pending\" | \"completed\";\nexport type ConversationTaskPriority = \"high\" | \"medium\" | \"low\";\n\nexport interface CreateConversationNoteInput extends ConversationAddress {\n eventId?: string;\n author: string;\n content: string;\n status?: string;\n type?: string;\n}\n\nexport interface CreateConversationTaskInput extends ConversationAddress {\n eventId?: string;\n author: string;\n /** Optional short headline, distinct from `description`. */\n title?: string;\n description: string;\n status?: ConversationTaskStatus;\n priority?: ConversationTaskPriority;\n assignee?: string;\n type?: string;\n}\n\nexport interface SetConversationTaskStatusInput {\n taskId: string;\n status: ConversationTaskStatus;\n resolvedBy?: string;\n /**\n * Email of the user performing the action. Truth excludes the actor\n * from the task-action push fan-out (legacy parity) — omit it and the\n * actor notifies themselves.\n */\n actor?: string;\n}\n\nexport interface UpdateConversationTaskInput {\n taskId: string;\n /** Conversation the task belongs to. Truth requires this on PATCH. */\n conversationId: string;\n /** Required by the Truth task router — see `messages/procedures/tasks.ts`. */\n author: string;\n description: string;\n /** Optional patch fields — pass only the ones you want to change. */\n title?: string;\n priority?: ConversationTaskPriority;\n status?: ConversationTaskStatus;\n assignee?: string;\n type?: string;\n /**\n * Email of the user performing the action. Truth excludes the actor\n * from the task-action push fan-out (legacy parity) — omit it and the\n * actor notifies themselves.\n */\n actor?: string;\n}\n\nexport interface SetConversationTaskArchivedInput {\n taskId: string;\n archived: boolean;\n /** Email of the acting user — excluded from the archive push fan-out. */\n actor?: string;\n}\n\nexport interface SendConversationMessageInput {\n fromNumber: string;\n toNumber: string;\n message?: string;\n media?: string;\n}\n\nexport interface SendConversationMessageResult {\n id: number;\n messageStatus: string;\n direction: string;\n}\n\nexport class ConversationsError extends Error {\n readonly status: number;\n\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Conversations ${operation} failed (HTTP ${status})`);\n this.name = \"ConversationsError\";\n this.status = status;\n }\n}\n\n/**\n * Guard: a conversation must be addressed by either `conversationId` or\n * `phonePair`. Reject locally so callers get an actionable error\n * instead of an avoidable server round-trip.\n */\nfunction assertConversationAddress(\n operation: string,\n input: ConversationAddress,\n): void {\n if (!input.conversationId && !input.phonePair) {\n throw new ConversationsError(\n operation,\n 0,\n \"Either `conversationId` or `phonePair` is required\",\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Subresources\n// ---------------------------------------------------------------------------\n\nclass ConversationNotesSubresource {\n constructor(\n private readonly post: <T>(path: string, body: unknown) => Promise<T>,\n ) {}\n\n /** Create a note on a conversation (addressed by id or phonePair). */\n async create(input: CreateConversationNoteInput): Promise<{ id: string }> {\n assertConversationAddress(\"notes.create\", input);\n return this.post<{ id: string }>(\"/conversations/notes\", input);\n }\n}\n\nclass ConversationTasksSubresource {\n constructor(\n private readonly post: <T>(path: string, body: unknown) => Promise<T>,\n private readonly patch: <T>(path: string, body: unknown) => Promise<T>,\n ) {}\n\n /** Create a task on a conversation. */\n async create(input: CreateConversationTaskInput): Promise<{ id: string }> {\n assertConversationAddress(\"tasks.create\", input);\n return this.post<{ id: string }>(\"/conversations/tasks\", input);\n }\n\n /** Mark a task pending or completed. */\n async setStatus(input: SetConversationTaskStatusInput): Promise<{\n ok: true;\n }> {\n return this.post<{ ok: true }>(\n `/conversations/tasks/${encodeURIComponent(input.taskId)}/status`,\n {\n id: input.taskId,\n status: input.status,\n ...(input.resolvedBy ? { resolvedBy: input.resolvedBy } : {}),\n ...(input.actor ? { actor: input.actor } : {}),\n },\n );\n }\n\n /** Archive or un-archive a task (hides it from My Tasks without deleting). */\n async setArchived(input: SetConversationTaskArchivedInput): Promise<{\n ok: true;\n changed: boolean;\n }> {\n return this.post<{ ok: true; changed: boolean }>(\n `/conversations/tasks/${encodeURIComponent(input.taskId)}/archive`,\n {\n id: input.taskId,\n archived: input.archived,\n ...(input.actor ? { actor: input.actor } : {}),\n },\n );\n }\n\n /**\n * Update task fields (priority, assignee, description, type). Wraps\n * `PATCH /api/conversations/tasks/{id}` so CommHub doesn't have to\n * direct-fetch for non-status field edits.\n */\n async update(input: UpdateConversationTaskInput): Promise<{ ok: true }> {\n return this.patch<{ ok: true }>(\n `/conversations/tasks/${encodeURIComponent(input.taskId)}`,\n {\n id: input.taskId,\n conversationId: input.conversationId,\n author: input.author,\n description: input.description,\n ...(input.title !== undefined ? { title: input.title } : {}),\n ...(input.priority ? { priority: input.priority } : {}),\n ...(input.status ? { status: input.status } : {}),\n ...(input.assignee !== undefined ? { assignee: input.assignee } : {}),\n ...(input.type !== undefined ? { type: input.type } : {}),\n ...(input.actor ? { actor: input.actor } : {}),\n },\n );\n }\n}\n\nclass ConversationMessagesSubresource {\n constructor(\n private readonly post: <T>(path: string, body: unknown) => Promise<T>,\n ) {}\n\n /**\n * Send an outbound SMS / MMS via the typed `sendMessage` oRPC procedure.\n *\n * Prefer this over the generic Dialpad proxy (`messages.dialpad.sendSms`)\n * — this hits the validated Truth route and consistently surfaces\n * `ConversationsError` on failure.\n */\n async send(\n input: SendConversationMessageInput,\n ): Promise<SendConversationMessageResult> {\n if (!input.message && !input.media) {\n throw new ConversationsError(\n \"messages.send\",\n 0,\n \"send requires `message` or `media`\",\n );\n }\n return this.post<SendConversationMessageResult>(\n \"/conversations/messages\",\n input,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// ConversationsResource\n// ---------------------------------------------------------------------------\n\nexport class ConversationsResource {\n readonly notes: ConversationNotesSubresource;\n readonly tasks: ConversationTasksSubresource;\n readonly messages: ConversationMessagesSubresource;\n\n /** 30s upstream timeout — matches NotesResource for consistency. */\n private static readonly REQUEST_TIMEOUT_MS = 30_000;\n\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly auth: RestAuth;\n private readonly convex: import(\"convex/browser\").ConvexHttpClient | null;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n convex?: import(\"convex/browser\").ConvexHttpClient,\n getAuthToken?: AuthTokenFetcher,\n ) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n this.auth = { apiKey, getAuthToken };\n this.convex = convex ?? null;\n const post = <T>(path: string, body: unknown) =>\n this.postRequest<T>(path, body);\n const patch = <T>(path: string, body: unknown) =>\n this.patchRequest<T>(path, body);\n this.notes = new ConversationNotesSubresource(post);\n this.tasks = new ConversationTasksSubresource(post, patch);\n this.messages = new ConversationMessagesSubresource(post);\n }\n\n /**\n * Mark a conversation read for the calling user (zeroes unreadCount,\n * stamps `lastReadAt`). Calls the public Convex `markRead` mutation\n * which enforces self-tenancy on the auth identity.\n */\n async markRead(input: {\n conversationId: string;\n userId: string;\n }): Promise<{ ok: true }> {\n if (!this.convex) {\n throw new ConversationsError(\n \"/markRead\",\n 0,\n \"ConversationsResource missing Convex client\",\n );\n }\n return (await this.convex.mutation(\n \"conversations:markRead\" as never,\n input as never,\n )) as { ok: true };\n }\n\n /**\n * Mark a conversation unread for the calling user (sets unreadCount=1).\n */\n async markUnread(input: {\n conversationId: string;\n userId: string;\n }): Promise<{ updated: true }> {\n if (!this.convex) {\n throw new ConversationsError(\n \"/markUnread\",\n 0,\n \"ConversationsResource missing Convex client\",\n );\n }\n return (await this.convex.mutation(\n \"conversations:markUnread\" as never,\n input as never,\n )) as { updated: true };\n }\n\n /**\n * Zero unread on every conversation the user has reads for, except\n * those whose `providerPhone` is in `excludedProviderPhones`.\n */\n async clearAllUnread(input: {\n userId: string;\n excludedProviderPhones?: string[];\n }): Promise<{ cleared: number }> {\n if (!this.convex) {\n throw new ConversationsError(\n \"/clearAllUnread\",\n 0,\n \"ConversationsResource missing Convex client\",\n );\n }\n return (await this.convex.mutation(\n \"conversations:clearAllUnread\" as never,\n input as never,\n )) as { cleared: number };\n }\n\n private async postRequest<T>(path: string, body: unknown): Promise<T> {\n if (!this.apiKey) {\n throw new ConversationsError(\n path,\n 0,\n \"Truth API key not configured — request blocked\",\n );\n }\n\n const controller = new AbortController();\n const timeout = setTimeout(\n () => controller.abort(),\n ConversationsResource.REQUEST_TIMEOUT_MS,\n );\n\n let res: Response;\n try {\n res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"POST\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n const isAbort =\n err instanceof Error &&\n (err.name === \"AbortError\" || err.name === \"TimeoutError\");\n const message = isAbort\n ? `Conversations ${path} timed out after ${ConversationsResource.REQUEST_TIMEOUT_MS}ms`\n : err instanceof Error\n ? err.message\n : \"Conversations request failed before response\";\n throw new ConversationsError(path, 0, message);\n } finally {\n clearTimeout(timeout);\n }\n\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new ConversationsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n /**\n * PATCH variant of `postRequest`. Mirrors the timeout + API-key\n * handling so callers like `tasks.update()` don't need to roll their\n * own fetch. The Truth task router treats unknown methods as 405, so\n * a dedicated PATCH path is required.\n */\n private async patchRequest<T>(path: string, body: unknown): Promise<T> {\n if (!this.apiKey) {\n throw new ConversationsError(\n path,\n 0,\n \"Truth API key not configured — request blocked\",\n );\n }\n const controller = new AbortController();\n const timeout = setTimeout(\n () => controller.abort(),\n ConversationsResource.REQUEST_TIMEOUT_MS,\n );\n let res: Response;\n try {\n res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"PATCH\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n const isAbort =\n err instanceof Error &&\n (err.name === \"AbortError\" || err.name === \"TimeoutError\");\n const message = isAbort\n ? `Conversations ${path} timed out after ${ConversationsResource.REQUEST_TIMEOUT_MS}ms`\n : err instanceof Error\n ? err.message\n : String(err);\n throw new ConversationsError(path, 0, message);\n } finally {\n clearTimeout(timeout);\n }\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new ConversationsError(\n path,\n res.status,\n `Conversations ${path} failed: ${text.slice(0, 200)}`,\n );\n }\n return (await res.json()) as T;\n }\n}\n","/**\n * Dialpad resource — typed access to Truth's Dialpad proxy endpoints.\n *\n * @example\n * ```ts\n * const sms = await truth.messages.dialpad.sendSms({ from_number, to_number, message });\n * const call = await truth.messages.dialpad.initiateCall(userId, phoneNumber);\n * await truth.messages.dialpad.hangupCall(callId);\n * const url = await truth.messages.dialpad.authenticateVoicemail(voicemailLink);\n * ```\n */\n\nimport type { AuthTokenFetcher } from \"../types/config\";\nimport { buildAuthHeaders, type RestAuth } from \"./rest-auth\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface SendSmsParams {\n from_number: string;\n to_number: string;\n message?: string;\n media?: string | string[];\n}\n\ninterface SendSmsResponse {\n id: string;\n message_status: string;\n [key: string]: unknown;\n}\n\ninterface InitiateCallResponse {\n call_id: number;\n [key: string]: unknown;\n}\n\ninterface CallStatusResponse {\n state: string;\n [key: string]: unknown;\n}\n\ninterface DialpadUser {\n id: number;\n emails: string[];\n first_name?: string;\n last_name?: string;\n [key: string]: unknown;\n}\n\ninterface DialpadUserListResponse {\n items: DialpadUser[];\n}\n\ninterface DialpadNumberInfo {\n user_id?: number;\n type?: string;\n [key: string]: unknown;\n}\n\ninterface VoicemailAuthResponse {\n success: boolean;\n authenticated_url: string | null;\n error?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Dialpad Resource\n// ---------------------------------------------------------------------------\n\nclass DialpadResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly auth: RestAuth;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n getAuthToken?: AuthTokenFetcher,\n ) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n this.auth = { apiKey, getAuthToken };\n }\n\n /**\n * Send an SMS or MMS message via Dialpad.\n */\n async sendSms(params: SendSmsParams): Promise<SendSmsResponse> {\n const body = {\n to_numbers: [params.to_number],\n from_number: params.from_number,\n infer_country_code: false,\n ...(params.message ? { text: params.message } : {}),\n ...(params.media ? { media: params.media } : {}),\n };\n\n return this.post<SendSmsResponse>(\"/sms\", body);\n }\n\n /**\n * Initiate an outbound call from a Dialpad user to a phone number.\n */\n async initiateCall(\n userId: number,\n phoneNumber: string,\n ): Promise<InitiateCallResponse> {\n return this.post<InitiateCallResponse>(`/users/${userId}/initiate_call`, {\n phone_number: phoneNumber,\n });\n }\n\n /**\n * Hang up an active call.\n */\n async hangupCall(callId: number): Promise<void> {\n await this.put(`/call/${callId}/actions/hangup`);\n }\n\n /**\n * Alias for `hangupCall` — mirrors the CommHub `endCall` action name so\n * the SDK swap is a direct rename.\n */\n async endCall(callId: number): Promise<void> {\n await this.hangupCall(callId);\n }\n\n /**\n * Send an MMS with a pre-uploaded attachment. Takes the S3 key returned\n * by `client.attachments.createUploadUrl(...)` + PUT, fetches a short-\n * lived download URL, and hands it to Dialpad as `media[]`.\n *\n * Replaces CommHub's `sendAttachment` Hasura Action (which stored bytes\n * as base64 in Postgres and served them through a GET endpoint).\n */\n async sendAttachmentWithUrl(params: {\n from_number: string;\n to_number: string;\n mediaUrl: string;\n message?: string;\n }): Promise<SendSmsResponse> {\n return this.sendSms({\n from_number: params.from_number,\n to_number: params.to_number,\n message: params.message,\n media: [params.mediaUrl],\n });\n }\n\n /**\n * Get the status of a call.\n */\n async getCallStatus(callId: number): Promise<CallStatusResponse | null> {\n try {\n return await this.get<CallStatusResponse>(`/call/${callId}`);\n } catch (error) {\n if (error instanceof DialpadProxyError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Get a Dialpad user by their user ID.\n */\n async getUser(userId: string | number): Promise<DialpadUser | null> {\n try {\n return await this.get<DialpadUser>(`/users/${userId}`);\n } catch (error) {\n if (error instanceof DialpadProxyError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Find a Dialpad user by email.\n */\n async getUserByEmail(email: string): Promise<DialpadUser | null> {\n const result = await this.get<DialpadUserListResponse>(\"/users\", {\n email,\n });\n return result.items?.[0] ?? null;\n }\n\n /**\n * Find a Dialpad user by phone number.\n */\n async getUserByPhoneNumber(phoneNumber: string): Promise<DialpadUser | null> {\n const result = await this.get<DialpadUserListResponse>(\"/users\", {\n number: phoneNumber,\n });\n return result.items?.[0] ?? null;\n }\n\n /**\n * Get information about a Dialpad phone number.\n */\n async getNumberInfo(phoneNumber: string): Promise<DialpadNumberInfo | null> {\n try {\n const cleanNumber = phoneNumber.replace(/[^\\d+]/g, \"\");\n return await this.get<DialpadNumberInfo>(\n `/numbers/${encodeURIComponent(cleanNumber)}`,\n );\n } catch (error) {\n if (error instanceof DialpadProxyError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Authenticate a voicemail download URL. Truth appends the Dialpad API key,\n * follows redirects, and returns the clean URL for client-side playback.\n */\n async authenticateVoicemail(voicemailLink: string): Promise<string | null> {\n const url = `${this.baseUrl}/api/messages/dialpad/voicemail/authenticate`;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: await buildAuthHeaders(this.auth, { json: true, accept: false }),\n body: JSON.stringify({ voicemail_link: voicemailLink }),\n });\n\n const result = (await response.json()) as VoicemailAuthResponse;\n\n if (!response.ok || !result.success) {\n return null;\n }\n\n return result.authenticated_url;\n }\n\n // -----------------------------------------------------------------------\n // Internal HTTP helpers\n // -----------------------------------------------------------------------\n\n private async get<T>(\n path: string,\n params?: Record<string, unknown>,\n ): Promise<T> {\n const url = new URL(`/api/messages/dialpad${path}`, this.baseUrl);\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const response = await fetch(url.toString(), {\n method: \"GET\",\n headers: await buildAuthHeaders(this.auth),\n });\n\n if (!response.ok) {\n throw new DialpadProxyError(\"GET\", path, response.status);\n }\n\n return (await response.json()) as T;\n }\n\n private async post<T>(path: string, body?: unknown): Promise<T> {\n const url = `${this.baseUrl}/api/messages/dialpad${path}`;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n throw new DialpadProxyError(\"POST\", path, response.status);\n }\n\n return (await response.json()) as T;\n }\n\n private async put<T = void>(path: string, body?: unknown): Promise<T> {\n const url = `${this.baseUrl}/api/messages/dialpad${path}`;\n\n const response = await fetch(url, {\n method: \"PUT\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n throw new DialpadProxyError(\"PUT\", path, response.status);\n }\n\n if (response.headers.get(\"content-type\")?.includes(\"json\")) {\n return (await response.json()) as T;\n }\n\n return undefined as T;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Messages Resource — namespace for messaging providers\n// ---------------------------------------------------------------------------\n\nclass MessagesResource {\n readonly dialpad: DialpadResource;\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly auth: RestAuth;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n getAuthToken?: AuthTokenFetcher,\n ) {\n this.dialpad = new DialpadResource(apiBaseUrl, apiKey, getAuthToken);\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n this.auth = { apiKey, getAuthToken };\n }\n\n /**\n * Get an authenticated URL for a Dialpad voicemail recording.\n *\n * Replaces CommHub's `getAuthenticatedVoicemailUrl` Hasura mutation\n * (which proxied to the legacy NestJS backend). Truth appends the\n * Dialpad API key, follows redirects, strips the key from the final\n * URL, and returns it for client-side audio playback.\n *\n * @param voicemailLink The raw Dialpad voicemail download URL (value\n * of the `voicemail_link` field on the call event row).\n * @returns An object with `url` (clean playback URL) and `expiresAt`\n * (ISO-8601 timestamp, ~5 min from request time, informational only).\n */\n async getVoicemailUrl(voicemailLink: string): Promise<{\n url: string;\n expiresAt: string;\n }> {\n const res = await fetch(`${this.baseUrl}/api/conversations/voicemail/url`, {\n method: \"POST\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: JSON.stringify({ voicemailLink }),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `messages.getVoicemailUrl failed (HTTP ${res.status}): ${text.slice(0, 200)}`,\n );\n }\n return (await res.json()) as { url: string; expiresAt: string };\n }\n\n /**\n * End a Dialpad call via the typed oRPC procedure (POST hangup, with\n * 404→`alreadyEnded` so the UI doesn't flash an error on a natural\n * race). Replaces CommHub's `useEndCallMutation` Hasura action.\n *\n * Prefer this over `messages.dialpad.endCall` which goes through the\n * generic proxy (PUT, no 404 handling).\n */\n async endCall(callId: number | string): Promise<{\n ok: true;\n alreadyEnded: boolean;\n }> {\n const res = await fetch(`${this.baseUrl}/api/conversations/calls/end`, {\n method: \"POST\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: JSON.stringify({ callId }),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `messages.endCall failed (HTTP ${res.status}): ${text.slice(0, 200)}`,\n );\n }\n return (await res.json()) as { ok: true; alreadyEnded: boolean };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Error\n// ---------------------------------------------------------------------------\n\nclass DialpadProxyError extends Error {\n readonly method: string;\n readonly path: string;\n readonly status: number;\n\n constructor(method: string, path: string, status: number) {\n super(\n `Dialpad proxy error: ${method} /api/messages/dialpad${path} returned ${status}`,\n );\n this.name = \"DialpadProxyError\";\n this.method = method;\n this.path = path;\n this.status = status;\n }\n}\n\nexport { DialpadResource, DialpadProxyError, MessagesResource };\nexport type {\n SendSmsParams,\n SendSmsResponse,\n InitiateCallResponse,\n CallStatusResponse,\n DialpadUser,\n DialpadNumberInfo,\n VoicemailAuthResponse,\n};\n","/**\n * EHR proxy resource — typed access to Truth's EHR proxy endpoints.\n *\n * Provides per-provider proxy methods so consumers never construct\n * proxy URLs manually. Every request carries the application's\n * `X-API-Key` — Truth's API gate rejects unauthenticated callers.\n *\n * @example\n * ```ts\n * const patient = await truth.ehr.elation.get('/patients/123/');\n * const notes = await truth.ehr.elation.post('/non_visit_notes/', noteData);\n * const hintPatient = await truth.ehr.hint.get('/provider/patients/456');\n * ```\n */\n\nimport type { AuthTokenFetcher } from \"../types/config\";\nimport { buildAuthHeaders, type RestAuth } from \"./rest-auth\";\n\n// ---------------------------------------------------------------------------\n// Provider proxy\n// ---------------------------------------------------------------------------\n\nclass EhrProviderProxy {\n private readonly baseUrl: string;\n private readonly provider: string;\n private readonly auth: RestAuth;\n\n constructor(\n apiBaseUrl: string,\n provider: string,\n apiKey: string,\n getAuthToken?: AuthTokenFetcher,\n ) {\n this.baseUrl = apiBaseUrl;\n this.provider = provider;\n this.auth = { apiKey, getAuthToken };\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n params?: Record<string, unknown>,\n ): Promise<T> {\n const url = new URL(`/api/ehr/${this.provider}${path}`, this.baseUrl);\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const response = await fetch(url.toString(), {\n method,\n headers: await buildAuthHeaders(this.auth, { json: body !== undefined }),\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n throw new EhrProxyError(this.provider, method, path, response.status);\n }\n\n return (await response.json()) as T;\n }\n\n /**\n * GET request to the EHR proxy.\n * @param path — path relative to the provider root (e.g., \"/patients/123/\")\n * @param params — optional query parameters\n */\n async get<T = unknown>(\n path: string,\n params?: Record<string, unknown>,\n ): Promise<T> {\n return this.request<T>(\"GET\", path, undefined, params);\n }\n\n /** POST request to the EHR proxy. */\n async post<T = unknown>(path: string, body?: unknown): Promise<T> {\n return this.request<T>(\"POST\", path, body);\n }\n\n /** PUT request to the EHR proxy. */\n async put<T = unknown>(path: string, body?: unknown): Promise<T> {\n return this.request<T>(\"PUT\", path, body);\n }\n\n /** PATCH request to the EHR proxy. */\n async patch<T = unknown>(path: string, body?: unknown): Promise<T> {\n return this.request<T>(\"PATCH\", path, body);\n }\n\n /** DELETE request to the EHR proxy. */\n async delete<T = unknown>(path: string): Promise<T> {\n return this.request<T>(\"DELETE\", path);\n }\n}\n\n// ---------------------------------------------------------------------------\n// EHR Resource — exposes per-provider proxies\n// ---------------------------------------------------------------------------\n\nclass EhrResource {\n /** Elation EHR proxy */\n readonly elation: EhrProviderProxy;\n\n /** Hint Health proxy */\n readonly hint: EhrProviderProxy;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n getAuthToken?: AuthTokenFetcher,\n ) {\n this.elation = new EhrProviderProxy(\n apiBaseUrl,\n \"elation\",\n apiKey,\n getAuthToken,\n );\n this.hint = new EhrProviderProxy(apiBaseUrl, \"hint\", apiKey, getAuthToken);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Error\n// ---------------------------------------------------------------------------\n\nclass EhrProxyError extends Error {\n readonly provider: string;\n readonly method: string;\n readonly path: string;\n readonly status: number;\n\n constructor(provider: string, method: string, path: string, status: number) {\n super(\n `EHR proxy error: ${method} /api/ehr/${provider}${path} returned ${status}`,\n );\n this.name = \"EhrProxyError\";\n this.provider = provider;\n this.method = method;\n this.path = path;\n this.status = status;\n }\n}\n\nexport { EhrResource, EhrProviderProxy, EhrProxyError };\n","/**\n * NotesResource — push formatted notes to Elation.\n *\n * Caller assembles the note body (text + bullets). Truth owns the Elation\n * OAuth token and the HTTP call so applications can drop Elation\n * credentials from their own config.\n */\n\nimport type { AuthTokenFetcher } from \"../types/config\";\nimport { buildAuthHeaders, type RestAuth } from \"./rest-auth\";\n\nexport interface NonVisitNoteBullet {\n text: string;\n category?: string;\n}\n\nexport interface PushNoteToElationInput {\n patient: number;\n physician: number;\n practice: number;\n note: { text: string; type?: string };\n document_date?: string;\n chart_date?: string;\n bullets?: NonVisitNoteBullet[];\n}\n\nexport interface PushNoteToElationResult {\n success: boolean;\n elationNoteId: number | null;\n}\n\nclass NotesError extends Error {\n readonly status: number;\n\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Notes ${operation} failed (HTTP ${status})`);\n this.name = \"NotesError\";\n this.status = status;\n }\n}\n\nexport interface PushConversationToElationInput {\n /** One-of: conversationId (Convex) or phonePair. */\n conversationId?: string;\n phonePair?: string;\n /** Actor user id — recorded on the audit row. */\n initiatedBy: string;\n /** Pre-assembled note body (transcript-style text). */\n noteText: string;\n bullets?: NonVisitNoteBullet[];\n notesPushed?: number;\n messagesPushed?: number;\n tasksPushed?: number;\n}\n\nexport interface PushConversationToElationResult {\n success: true;\n batchId: string;\n elationNoteId: number | null;\n notesPushed: number;\n messagesPushed: number;\n tasksPushed: number;\n}\n\nclass NotesResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly auth: RestAuth;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n getAuthToken?: AuthTokenFetcher,\n ) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n this.auth = { apiKey, getAuthToken };\n }\n\n /** 30s upstream timeout — Elation API has occasional slow hops; we\n * don't want a pending mutation to hold the UI thread indefinitely. */\n private static readonly REQUEST_TIMEOUT_MS = 30_000;\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const controller = new AbortController();\n const timeout = setTimeout(\n () => controller.abort(),\n NotesResource.REQUEST_TIMEOUT_MS,\n );\n\n let res: Response;\n try {\n res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"POST\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n // Transport failures (DNS, TCP, TLS, AbortError) become typed\n // NotesError so consumers don't have to special-case raw fetch\n // exceptions. status=0 signals \"never made it to the server\".\n const isAbort =\n err instanceof Error &&\n (err.name === \"AbortError\" || err.name === \"TimeoutError\");\n const message = isAbort\n ? `Notes ${path} timed out after ${NotesResource.REQUEST_TIMEOUT_MS}ms`\n : err instanceof Error\n ? err.message\n : \"Notes request failed before response\";\n throw new NotesError(path, 0, message);\n } finally {\n clearTimeout(timeout);\n }\n\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new NotesError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n /**\n * Low-level — caller has already resolved Elation patient / physician\n * / practice. Use `pushConversationToElation` if you only have a\n * conversation handle.\n */\n async pushToElation(\n input: PushNoteToElationInput,\n ): Promise<PushNoteToElationResult> {\n return await this.post<PushNoteToElationResult>(\n \"/notes/push-to-elation\",\n input,\n );\n }\n\n /**\n * Orchestrator — pass a conversation handle + pre-assembled note text.\n * Truth resolves the linked patient via Convex, looks up\n * physician/practice in Elation, posts the note, and writes an audit\n * row to `elationSyncEvents`. Replaces CommHub's `pushNotesToElation`\n * Hasura action.\n */\n async pushConversationToElation(\n input: PushConversationToElationInput,\n ): Promise<PushConversationToElationResult> {\n return await this.post<PushConversationToElationResult>(\n \"/notes/push-conversation-to-elation\",\n input,\n );\n }\n}\n\nexport { NotesResource, NotesError };\n","/**\n * NotificationsResource — wraps Truth's /api/notifications/* endpoints.\n *\n * Server-side use (for example from a CommHub backend job or\n * another service): `truth.notifications.send({ userId, title, body })`.\n *\n * Client-side React usage lives in `@hipnation-truth/sdk/react` via\n * the `useNotifications` hook which mirrors `expo-notifications`.\n */\n\nimport type { AuthTokenFetcher } from \"../types/config\";\nimport { buildAuthHeaders, type RestAuth } from \"./rest-auth\";\n\nexport type NotificationPlatform = \"ios\" | \"android\" | \"web\";\n\nexport interface RegisterDeviceInput {\n userId: string;\n platform: NotificationPlatform;\n nativeToken?: string;\n webPushSubscription?: {\n endpoint: string;\n keys: { p256dh: string; auth: string };\n };\n appVersion?: string;\n osVersion?: string;\n locale?: string;\n timezone?: string;\n /**\n * iOS only — which APNs endpoint the `nativeToken` was issued\n * against. The token bytes don't carry this; it's determined by\n * the build's `aps-environment` entitlement\n * (`development` ⇒ sandbox, `production` ⇒ production). When the\n * consumer is an Expo app, detect via\n * `Application.getIosPushNotificationServiceEnvironmentAsync()`\n * from `expo-application` and pass the normalised value here.\n * Omitting this falls back to the application's default\n * `pushConfig.ios.environment` server-side.\n */\n apnsEnvironment?: \"sandbox\" | \"production\";\n}\n\nexport interface RegisterDeviceResult {\n deviceId: string;\n action: \"inserted\" | \"updated\";\n snsEndpointArn?: string;\n}\n\nexport interface UnregisterDeviceInput {\n nativeToken?: string;\n deviceId?: string;\n}\n\nexport interface SendNotificationInput {\n userId: string;\n title: string;\n body: string;\n data?: Record<string, unknown>;\n badge?: number;\n sound?: string;\n}\n\nexport interface SendNotificationResult {\n delivered: number;\n failed?: number;\n suppressed?: boolean;\n suppressionReason?: string;\n}\n\nexport interface NotificationPreferences {\n channels: { sms: boolean; push: boolean; email: boolean; inApp: boolean };\n quietHours?: {\n enabled: boolean;\n start: string;\n end: string;\n timezone: string;\n };\n doNotDisturbUntil?: string;\n updatedAt: string;\n}\n\nexport interface UpdatePreferencesInput {\n userId: string;\n channels?: NotificationPreferences[\"channels\"];\n quietHours?: NotificationPreferences[\"quietHours\"];\n doNotDisturbUntil?: string | null;\n}\n\nexport interface ScheduleNotificationInput extends SendNotificationInput {\n /** ISO 8601 timestamp; must be strictly in the future. */\n scheduledAt: string;\n}\n\nexport interface ScheduleNotificationResult {\n jobId: string;\n scheduledAt: string;\n}\n\nexport interface CancelScheduledNotificationResult {\n cancelled: boolean;\n reason?: string;\n status?: string;\n}\n\nexport type ScheduledJobStatus =\n | \"pending\"\n | \"executed\"\n | \"cancelled\"\n | \"failed\";\n\nexport interface ScheduledNotification {\n jobId: string;\n userId: string;\n title: string;\n body: string;\n data?: unknown;\n badge?: number;\n sound?: string;\n scheduledAt: string;\n status: ScheduledJobStatus;\n resultHistoryId?: string;\n errorMessage?: string;\n createdAt: string;\n}\n\nexport interface PushEventPayload {\n title: string;\n body: string;\n data?: unknown;\n}\n\nclass NotificationsError extends Error {\n readonly status: number;\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Notifications ${operation} failed (HTTP ${status})`);\n this.name = \"NotificationsError\";\n this.status = status;\n }\n}\n\nclass NotificationsResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly auth: RestAuth;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n getAuthToken?: AuthTokenFetcher,\n ) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n this.auth = { apiKey, getAuthToken };\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"POST\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new NotificationsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n private async get<T>(\n path: string,\n params: Record<string, string>,\n ): Promise<T> {\n const url = new URL(`${this.baseUrl}/api${path}`);\n for (const [k, v] of Object.entries(params)) {\n url.searchParams.set(k, v);\n }\n const res = await fetch(url.toString(), {\n method: \"GET\",\n headers: await buildAuthHeaders(this.auth),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new NotificationsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n private async delete<T>(path: string): Promise<T> {\n const res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"DELETE\",\n headers: await buildAuthHeaders(this.auth),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new NotificationsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n /**\n * Register a device (or refresh its metadata) for push delivery.\n * Safe to call repeatedly — the server dedupes by native token.\n */\n async registerDevice(\n input: RegisterDeviceInput,\n ): Promise<RegisterDeviceResult> {\n return this.post(\"/notifications/devices/register\", input);\n }\n\n /** Revoke a device — on sign-out or when the OS reports an invalid token. */\n async unregisterDevice(\n input: UnregisterDeviceInput,\n ): Promise<{ revoked: boolean }> {\n return this.post(\"/notifications/devices/unregister\", input);\n }\n\n /**\n * Send a push notification to every active device belonging to\n * `userId`. Honors the user's notificationPreferences (quiet hours,\n * DND, channel off) before publishing.\n */\n async send(input: SendNotificationInput): Promise<SendNotificationResult> {\n return this.post(\"/notifications/send\", input);\n }\n\n /** Read a user's notification preferences. Returns defaults when no row exists. */\n async getPreferences(userId: string): Promise<NotificationPreferences> {\n return this.get(\"/notifications/preferences\", { userId });\n }\n\n async updatePreferences(\n input: UpdatePreferencesInput,\n ): Promise<{ ok: boolean }> {\n return this.post(\"/notifications/preferences\", input);\n }\n\n /**\n * Schedule a future push notification. Convex's native scheduler\n * fires the send at `scheduledAt` and runs the same delivery\n * pipeline as `send()` (preferences, devices, history audit).\n *\n * Throws `NotificationsError` with status 400 if `scheduledAt` is\n * not strictly in the future.\n */\n async schedule(\n input: ScheduleNotificationInput,\n ): Promise<ScheduleNotificationResult> {\n return this.post(\"/notifications/schedule\", input);\n }\n\n /**\n * Cancel a pending scheduled notification. Returns `cancelled: false`\n * (no error) if the job has already executed, was previously\n * cancelled, or no longer exists — `reason` describes which case.\n */\n async cancelScheduled(\n jobId: string,\n ): Promise<CancelScheduledNotificationResult> {\n return this.delete(`/notifications/schedule/${encodeURIComponent(jobId)}`);\n }\n\n /**\n * List scheduled notifications for a user — pending, executed,\n * cancelled, or failed. Most-recent first. Default limit 100.\n */\n async listScheduled(\n userId: string,\n options?: { limit?: number },\n ): Promise<ScheduledNotification[]> {\n const params: Record<string, string> = { userId };\n if (options?.limit !== undefined) {\n params.limit = String(options.limit);\n }\n return this.get(\"/notifications/schedule\", params);\n }\n\n async getVapidKey(): Promise<string | null> {\n try {\n const result = await this.get<{ vapidPublicKey: string | null }>(\n \"/notifications/vapid-key\",\n {},\n );\n return result.vapidPublicKey;\n } catch {\n return null;\n }\n }\n\n onPushReceived(callback: (payload: PushEventPayload) => void): () => void {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return () => {};\n }\n const handler = (event: MessageEvent) => {\n if (event.data?.type === \"TRUTH_PUSH_RECEIVED\") {\n callback(event.data.payload);\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n return () =>\n navigator.serviceWorker.removeEventListener(\"message\", handler);\n }\n\n onPushTapped(callback: (payload: PushEventPayload) => void): () => void {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return () => {};\n }\n const handler = (event: MessageEvent) => {\n if (event.data?.type === \"TRUTH_PUSH_TAPPED\") {\n callback(event.data.payload);\n }\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n return () =>\n navigator.serviceWorker.removeEventListener(\"message\", handler);\n }\n}\n\nexport { NotificationsResource, NotificationsError };\n","/**\n * PatientDetailsResource — merged Hint + Elation patient lookups.\n *\n * Backed by the Truth API at /api/patients/details/*, authenticated with\n * X-API-Key. Replaces CommHub's getPatientDetails / getPatientBasicDetails\n * / getPatientMedicalDetails actions.\n */\n\nimport type { AuthTokenFetcher } from \"../types/config\";\nimport { buildAuthHeaders, type RestAuth } from \"./rest-auth\";\n\nexport interface PatientDetailsInput {\n hintId?: string;\n elationId?: string;\n}\n\nexport interface PatientDetailsResult {\n hint: unknown | null;\n elation: unknown | null;\n resolvedElationId: string | null;\n}\n\nexport interface PatientBasicDetailsResult {\n hint: unknown | null;\n elationId: string | null;\n}\n\nexport interface PatientMedicalDetailsResult {\n problems: unknown | null;\n medications: unknown | null;\n allergies: unknown | null;\n appointments: unknown | null;\n}\n\nclass PatientDetailsError extends Error {\n readonly status: number;\n\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Patient ${operation} failed (HTTP ${status})`);\n this.name = \"PatientDetailsError\";\n this.status = status;\n }\n}\n\nclass PatientDetailsResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly auth: RestAuth;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n getAuthToken?: AuthTokenFetcher,\n ) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n this.auth = { apiKey, getAuthToken };\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const res = await fetch(`${this.baseUrl}/api${path}`, {\n method: \"POST\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new PatientDetailsError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n async get(input: PatientDetailsInput): Promise<PatientDetailsResult> {\n return await this.post<PatientDetailsResult>(\"/patients/details\", input);\n }\n\n async getBasic(\n input: PatientDetailsInput,\n ): Promise<PatientBasicDetailsResult> {\n return await this.post<PatientBasicDetailsResult>(\n \"/patients/details/basic\",\n input,\n );\n }\n\n async getMedical(elationId: string): Promise<PatientMedicalDetailsResult> {\n return await this.post<PatientMedicalDetailsResult>(\n \"/patients/details/medical\",\n { elationId },\n );\n }\n\n /**\n * Trigger a server-side refresh of the patient's Elation medical\n * records (medications, problems, allergies, appointments) into the\n * Convex cache. Fire-and-forget — the UI should read via the Convex-\n * reactive `usePatientMedical` hook.\n */\n async refreshMedical(elationId: number): Promise<{\n totals: {\n medications: number;\n problems: number;\n allergies: number;\n appointments: number;\n };\n }> {\n return await this.post<{\n totals: {\n medications: number;\n problems: number;\n allergies: number;\n appointments: number;\n };\n }>(\"/patients/medical/refresh\", { elationId });\n }\n}\n\nexport { PatientDetailsResource, PatientDetailsError };\n","/**\n * PatientResource provides data access to normalized patient records\n * backed by Convex.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type { PaginatedResult } from \"../types/config\";\nimport type { Patient, PatientListOptions } from \"../types/patient\";\n\nclass PatientResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convexClient: ConvexHttpClient) {\n this.convex = convexClient;\n }\n\n /**\n * Get a patient by their Truth platform ID.\n */\n async get(id: string): Promise<Patient | null> {\n try {\n const result = await this.convex.query(\n \"patients:getById\" as never,\n {\n id,\n } as never,\n );\n return (result as Patient) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * Get a patient by their Elation EHR ID.\n */\n async getByElationId(elationId: string): Promise<Patient | null> {\n try {\n const result = await this.convex.query(\n \"patients:getByElationId\" as never,\n { elationId } as never,\n );\n return (result as Patient) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * Get a patient by their Hint EHR ID.\n */\n async getByHintId(hintId: string): Promise<Patient | null> {\n try {\n const result = await this.convex.query(\n \"patients:getByHintId\" as never,\n {\n hintId,\n } as never,\n );\n return (result as Patient) ?? null;\n } catch {\n return null;\n }\n }\n\n /**\n * List patients with optional search, pagination, and limit.\n */\n async list(options?: PatientListOptions): Promise<PaginatedResult<Patient>> {\n try {\n const result = await this.convex.query(\n \"patients:list\" as never,\n {\n search: options?.search,\n limit: options?.limit,\n cursor: options?.cursor,\n } as never,\n );\n\n const typed = result as PaginatedResult<Patient> | undefined;\n\n return typed ?? { data: [], cursor: null, hasMore: false };\n } catch {\n return { data: [], cursor: null, hasMore: false };\n }\n }\n}\n\nexport { PatientResource };\n","/**\n * PhysiciansResource — Convex-backed physician lookups.\n *\n * Populated from Elation by Truth's daily PhysiciansBackfillCron. Replaces\n * per-physician Elation HTTP hops with a single Convex batch query.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\n\nexport interface 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\nclass PhysiciansResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convex: ConvexHttpClient) {\n this.convex = convex;\n }\n\n /**\n * Resolve a batch of physicians by Elation IDs. Missing ids are dropped.\n */\n async getByElationIds(ids: number[]): Promise<Physician[]> {\n if (ids.length === 0) {\n return [];\n }\n try {\n const rows = (await this.convex.query(\n \"physicians:getByElationIds\" as never,\n { ids } as never,\n )) as Physician[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n\n async getByElationId(id: number): Promise<Physician | null> {\n try {\n const row = (await this.convex.query(\n \"physicians:getByElationId\" as never,\n { id } as never,\n )) as Physician | null;\n return row ?? null;\n } catch {\n return null;\n }\n }\n\n async listByPractice(practice: number, limit?: number): Promise<Physician[]> {\n try {\n const rows = (await this.convex.query(\n \"physicians:listByPractice\" as never,\n { practice, limit } as never,\n )) as Physician[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n}\n\nexport { PhysiciansResource };\n","/**\n * RemindersResource — schedule, cancel, and list conversation reminders.\n *\n * Backed by Convex mutations/queries (durable scheduled functions) — replaces\n * the in-memory setTimeout scheduler that used to live in CommHub's NestJS\n * backend.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type {\n Reminder,\n ScheduleReminderInput,\n ScheduleReminderResult,\n} from \"../types/reminder\";\n\nclass RemindersResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convexClient: ConvexHttpClient) {\n this.convex = convexClient;\n }\n\n /**\n * Schedule a reminder to fire at `remindAt`. Returns the reminder id,\n * which callers should store if they may want to cancel it later.\n */\n async schedule(\n input: ScheduleReminderInput,\n ): Promise<ScheduleReminderResult> {\n const remindAt =\n input.remindAt instanceof Date\n ? input.remindAt.toISOString()\n : input.remindAt;\n\n const result = (await this.convex.mutation(\n \"reminders:schedule\" as never,\n {\n conversationId: input.conversationId,\n remindAt,\n note: input.note,\n createdBy: input.createdBy,\n } as never,\n )) as ScheduleReminderResult;\n\n return result;\n }\n\n /**\n * Cancel a pending reminder. No-op if the reminder has already fired or\n * been cancelled.\n */\n async cancel(\n reminderId: string,\n cancelledBy: string,\n ): Promise<{ reminderId: string; status: string }> {\n return (await this.convex.mutation(\n \"reminders:cancel\" as never,\n { reminderId, cancelledBy } as never,\n )) as { reminderId: string; status: string };\n }\n\n /**\n * List reminders for a conversation (most recent first).\n */\n async listByConversation(conversationId: string): Promise<Reminder[]> {\n try {\n const rows = (await this.convex.query(\n \"reminders:listByConversation\" as never,\n { conversationId } as never,\n )) as Reminder[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n}\n\nexport { RemindersResource };\n","/**\n * TasksResource — create, update status, list, get tasks.\n *\n * Backed by Convex. Replaces CommHub's createTaskWithNotification +\n * updateTaskStatusWithNotification actions. Push notification side-effect\n * is emitted downstream via Kinesis.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type {\n ConversationTaskMarkSeenInput,\n CreateTaskInput,\n Task,\n TaskStatus,\n UpdateTaskStatusInput,\n} from \"../types/task\";\n\nclass TasksResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convexClient: ConvexHttpClient) {\n this.convex = convexClient;\n }\n\n async create(\n input: CreateTaskInput,\n ): Promise<{ taskId: string; status: TaskStatus }> {\n return (await this.convex.mutation(\n \"tasks:create\" as never,\n input as never,\n )) as { taskId: string; status: TaskStatus };\n }\n\n async updateStatus(input: UpdateTaskStatusInput): Promise<{\n taskId: string;\n status: TaskStatus;\n previousStatus: TaskStatus;\n }> {\n return (await this.convex.mutation(\n \"tasks:updateStatus\" as never,\n input as never,\n )) as {\n taskId: string;\n status: TaskStatus;\n previousStatus: TaskStatus;\n };\n }\n\n async get(taskId: string): Promise<Task | null> {\n try {\n const row = (await this.convex.query(\n \"tasks:get\" as never,\n { taskId } as never,\n )) as Task | null;\n return row ?? null;\n } catch {\n return null;\n }\n }\n\n async listByAssignee(\n assignedTo: string,\n options?: { status?: TaskStatus; limit?: number },\n ): Promise<Task[]> {\n try {\n const rows = (await this.convex.query(\n \"tasks:listByAssignee\" as never,\n {\n assignedTo,\n status: options?.status,\n limit: options?.limit,\n } as never,\n )) as Task[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n\n async listOpen(limit?: number): Promise<Task[]> {\n try {\n const rows = (await this.convex.query(\n \"tasks:listOpen\" as never,\n { limit } as never,\n )) as Task[] | null;\n return rows ?? [];\n } catch {\n return [];\n }\n }\n\n /**\n * Mark a conversation task as seen by the given user. Appends `userId`\n * to the `seenBy` array on the `conversationTasks` row if not already\n * present. Replaces CommHub's `useMark_Event_Activity_SeenMutation`.\n *\n * @returns `{ ok: true }` on success.\n * @throws if the task does not exist in Convex.\n */\n async markSeen(input: ConversationTaskMarkSeenInput): Promise<{ ok: true }> {\n return (await this.convex.mutation(\n \"conversationTasks:markSeen\" as never,\n input as never,\n )) as { ok: true };\n }\n}\n\nexport { TasksResource };\n","/**\n * TranslationResource — Azure Translator proxy through Truth API.\n *\n * All three operations are HTTP calls to Truth's oRPC endpoints at\n * `/api/translation/*`, authenticated with the same X-API-Key as the\n * event tracker.\n */\n\nimport type { AuthTokenFetcher } from \"../types/config\";\nimport type {\n DetectionResult,\n TranslateBatchInput,\n TranslateTextInput,\n TranslationResult,\n} from \"../types/translation\";\nimport { buildAuthHeaders, type RestAuth } from \"./rest-auth\";\n\nclass TranslationError extends Error {\n readonly status: number;\n readonly operation: string;\n\n constructor(operation: string, status: number, message?: string) {\n super(message ?? `Translation ${operation} failed (HTTP ${status})`);\n this.name = \"TranslationError\";\n this.status = status;\n this.operation = operation;\n }\n}\n\nclass TranslationResource {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly auth: RestAuth;\n\n constructor(\n apiBaseUrl: string,\n apiKey: string,\n getAuthToken?: AuthTokenFetcher,\n ) {\n this.baseUrl = apiBaseUrl;\n this.apiKey = apiKey;\n this.auth = { apiKey, getAuthToken };\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const url = `${this.baseUrl}/api${path}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: await buildAuthHeaders(this.auth, { json: true }),\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new TranslationError(path, res.status, text.slice(0, 200));\n }\n return (await res.json()) as T;\n }\n\n async translate(input: TranslateTextInput): Promise<TranslationResult> {\n return await this.post<TranslationResult>(\"/translation/translate\", input);\n }\n\n async translateBatch(\n input: TranslateBatchInput,\n ): Promise<TranslationResult[]> {\n const response = await this.post<{ results: TranslationResult[] }>(\n \"/translation/translate-batch\",\n input,\n );\n return response.results;\n }\n\n async detect(text: string): Promise<DetectionResult> {\n return await this.post<DetectionResult>(\"/translation/detect\", { text });\n }\n}\n\nexport { TranslationResource, TranslationError };\n","/**\n * UserSettingsResource — typed client for the Truth `userSettings` Convex\n * table. Exposes the one mutation CommHub's Settings screen needs:\n * `updateNotifications({ userId, notificationsEnabled })`.\n *\n * Server-side / imperative use:\n * truth.userSettings.updateNotifications({ userId, notificationsEnabled: true })\n *\n * Reactive React use lives in `@hipnation-truth/sdk/react` via\n * `useUserSettings`.\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport { makeFunctionReference } from \"convex/server\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UserSettings {\n _id: string;\n userId: string;\n notificationsEnabled: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface UpdateNotificationsInput {\n userId: string;\n notificationsEnabled: boolean;\n}\n\nexport interface UpdateNotificationsResult {\n action: \"inserted\" | \"updated\";\n id: string;\n}\n\n// ---------------------------------------------------------------------------\n// Convex function references (string-based, SDK stays decoupled from codegen)\n// ---------------------------------------------------------------------------\n\nconst upsertNotificationsRef = makeFunctionReference<\n \"mutation\",\n { userId: string; notificationsEnabled: boolean },\n UpdateNotificationsResult\n>(\"userSettings:upsertNotifications\");\n\n// ---------------------------------------------------------------------------\n// Resource\n// ---------------------------------------------------------------------------\n\nclass UserSettingsResource {\n private readonly convex: ConvexHttpClient;\n\n constructor(convex: ConvexHttpClient) {\n this.convex = convex;\n }\n\n /**\n * Upsert notification preferences for a user. Creates the row if it\n * doesn't exist; patches `notificationsEnabled` + `updatedAt` otherwise.\n */\n async updateNotifications(\n input: UpdateNotificationsInput,\n ): Promise<UpdateNotificationsResult> {\n return this.convex.mutation(upsertNotificationsRef, input);\n }\n}\n\nexport { UserSettingsResource };\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 * 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 * TruthClient -- main entry point for the @hipnation-truth/sdk package.\n *\n * Provides:\n * - `.patients` Resource-based patient data access (Convex-backed)\n * - `.appointments` Resource-based appointment data access (Convex-backed)\n * - `.ehr` EHR proxy access (Elation, Hint)\n * - `.messages` Messaging proxy access (Dialpad)\n * - `.track()` Fire-and-forget event tracking (batched HTTP -> Truth API)\n * - `.identify()` Set default actor context for subsequent events\n * - `.flush()` Force flush of buffered events (for graceful shutdown)\n */\n\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport { AuthAwareConvexHttpClient } from \"./auth-convex-client\";\nimport { AppointmentResource } from \"./resources/appointments\";\nimport { AttachmentsResource } from \"./resources/attachments\";\nimport { ConversationsResource } from \"./resources/conversations\";\nimport { MessagesResource } from \"./resources/dialpad\";\nimport { EhrResource } from \"./resources/ehr\";\nimport { NotesResource } from \"./resources/notes\";\nimport { NotificationsResource } from \"./resources/notifications\";\nimport { PatientDetailsResource } from \"./resources/patient-details\";\nimport { PatientResource } from \"./resources/patients\";\nimport { PhysiciansResource } from \"./resources/physicians\";\nimport { RemindersResource } from \"./resources/reminders\";\nimport { TasksResource } from \"./resources/tasks\";\nimport { TranslationResource } from \"./resources/translation\";\nimport { UserSettingsResource } from \"./resources/user-settings\";\nimport type {\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"./tracking/events\";\nimport {\n DEFAULT_BATCH_SIZE,\n DEFAULT_FLUSH_INTERVAL_MS,\n Tracker,\n} from \"./tracking/tracker\";\nimport type { ActorContext, TruthClientConfig } from \"./types/config\";\nimport {\n isWebPushSupported,\n registerServiceWorker,\n subscribeToPush,\n subscriptionToJSON,\n} from \"./web-push\";\n\n// ---------------------------------------------------------------------------\n// Environment -> Convex URL mapping\n// ---------------------------------------------------------------------------\n\n// Environment → Convex deployment URL.\n//\n// Topology mirrors API_URLS in tracker.ts:\n// local / staging / stg / sandbox → sandbox Convex (courteous-duck-623)\n// uat / production → production Convex (gallant-gecko-217)\n//\n// UAT shares production resources. Staging is the isolated sandbox env.\nconst 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// TruthClient\n// ---------------------------------------------------------------------------\n\nclass TruthClient {\n /** Patient data resource (Convex-backed) */\n readonly patients: PatientResource;\n\n /** Appointment data resource (Convex-backed) */\n readonly appointments: AppointmentResource;\n\n /** EHR proxy — typed access to Elation and Hint APIs through Truth */\n readonly ehr: EhrResource;\n\n /** Messaging proxy — typed access to Dialpad APIs through Truth */\n readonly messages: MessagesResource;\n\n /** Conversation reminders (Convex-backed, durable scheduler) */\n readonly reminders: RemindersResource;\n\n /** Translation (Azure Translator proxy) */\n readonly translation: TranslationResource;\n\n /** Tasks (Convex-backed) */\n readonly tasks: TasksResource;\n\n /** Patient details — merged Hint + Elation lookups via Truth API */\n readonly patientDetails: PatientDetailsResource;\n\n /** Attachments — presigned S3 upload/download + Convex metadata */\n readonly attachments: AttachmentsResource;\n\n /** Notes — push formatted notes to Elation */\n readonly notes: NotesResource;\n\n /** Physicians (Convex-backed cache from Elation) */\n readonly physicians: PhysiciansResource;\n\n /** Push / web notifications (AWS End User Messaging) */\n readonly notifications: NotificationsResource;\n\n /** Conversation-tied writes — notes, tasks, outbound messages. */\n readonly conversations: ConversationsResource;\n\n /** User settings — notification preferences (Convex-backed) */\n readonly userSettings: UserSettingsResource;\n\n private readonly convex: ConvexHttpClient;\n private readonly tracker: Tracker;\n private _vapidPublicKey: string | null = null;\n private _webPushReady: Promise<void> | null = null;\n private readonly _serviceWorkerPath: string;\n\n constructor(config: TruthClientConfig) {\n // Resolve Convex URL\n const convexUrl =\n config.convexUrl ?? CONVEX_URLS[config.environment] ?? CONVEX_URLS.local;\n\n // Initialize Convex HTTP client for data access. When the host app\n // injects `getAuthToken`, every call carries the user's Clerk JWT\n // (refreshed per call — see AuthAwareConvexHttpClient).\n this.convex = new AuthAwareConvexHttpClient(convexUrl, config.getAuthToken);\n\n // Initialize event tracker\n this.tracker = new Tracker({\n apiKey: config.apiKey,\n environment: config.environment,\n source: config.source ?? \"unknown\",\n sourceVersion: config.sourceVersion ?? \"unknown\",\n tenantId: config.tenantId ?? \"\",\n batchSize: config.batchSize ?? DEFAULT_BATCH_SIZE,\n flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,\n apiBaseUrl: config.apiBaseUrl,\n });\n\n const apiUrl = this.tracker.apiUrl;\n\n // Initialize resources\n this.patients = new PatientResource(this.convex);\n this.appointments = new AppointmentResource(this.convex);\n this.ehr = new EhrResource(apiUrl, config.apiKey, config.getAuthToken);\n this.messages = new MessagesResource(\n apiUrl,\n config.apiKey,\n config.getAuthToken,\n );\n this.reminders = new RemindersResource(this.convex);\n this.translation = new TranslationResource(\n apiUrl,\n config.apiKey,\n config.getAuthToken,\n );\n this.tasks = new TasksResource(this.convex);\n this.patientDetails = new PatientDetailsResource(\n apiUrl,\n config.apiKey,\n config.getAuthToken,\n );\n this.attachments = new AttachmentsResource(\n apiUrl,\n config.apiKey,\n this.convex,\n config.getAuthToken,\n );\n this.notes = new NotesResource(apiUrl, config.apiKey, config.getAuthToken);\n this.physicians = new PhysiciansResource(this.convex);\n this.notifications = new NotificationsResource(\n apiUrl,\n config.apiKey,\n config.getAuthToken,\n );\n this.conversations = new ConversationsResource(\n apiUrl,\n config.apiKey,\n this.convex,\n config.getAuthToken,\n );\n this.userSettings = new UserSettingsResource(this.convex);\n this._serviceWorkerPath = config.serviceWorkerPath ?? \"/truth-sw.js\";\n\n if (\n typeof window !== \"undefined\" &&\n isWebPushSupported() &&\n config.autoInitServiceWorker !== false\n ) {\n this._webPushReady = this.initWebPush();\n }\n }\n\n get vapidPublicKey(): string | null {\n return this._vapidPublicKey;\n }\n\n get webPushReady(): Promise<void> | null {\n return this._webPushReady;\n }\n\n private async initWebPush(): Promise<void> {\n try {\n const key = await this.notifications.getVapidKey();\n if (!key) {\n return;\n }\n this._vapidPublicKey = key;\n\n const registration = await registerServiceWorker(this._serviceWorkerPath);\n await navigator.serviceWorker.ready;\n const subscription = await subscribeToPush(registration, key);\n const subJSON = subscriptionToJSON(subscription);\n\n await this.notifications.registerDevice({\n userId: \"__pending__\",\n platform: \"web\",\n webPushSubscription: subJSON,\n locale: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n });\n } catch {\n // Web push init is best-effort — don't break the client.\n }\n }\n\n /**\n * The resolved Truth API base URL for this environment.\n * Use this when making HTTP calls to Truth's proxy endpoints\n * (e.g., EHR proxy, messages proxy).\n *\n * @example\n * ```ts\n * const url = `${truth.apiBaseUrl}/api/ehr/elation/patients/123`;\n * ```\n */\n get apiBaseUrl(): string {\n return this.tracker.apiUrl;\n }\n\n /**\n * Track an event. Fire-and-forget -- the event is buffered internally\n * and flushed in batches to the Truth API.\n *\n * @example\n * ```ts\n * truth.track('conversation.message_sent.v1', {\n * channel: 'sms',\n * direction: 'outbound',\n * message_chars: 140,\n * has_attachment: false,\n * provider_system: 'dialpad',\n * });\n * ```\n */\n track<T extends EventType>(\n eventType: T,\n payload: EventPayloadMap[T],\n options?: TrackOptions,\n ): void {\n this.tracker.track(eventType, payload, options);\n }\n\n /**\n * Set the default actor context for all subsequent tracked events.\n * Can be overridden per-event via TrackOptions.\n *\n * @example\n * ```ts\n * truth.identify('user_123', 'user');\n * ```\n */\n identify(actorId: string, actorType: ActorContext[\"actorType\"]): void {\n this.tracker.setActor({ actorId, actorType });\n }\n\n /**\n * Flush all buffered events immediately. Returns a Promise that resolves\n * when the flush completes. Use this for graceful shutdown.\n *\n * @example\n * ```ts\n * process.on('SIGTERM', async () => {\n * await truth.flush();\n * process.exit(0);\n * });\n * ```\n */\n async flush(): Promise<void> {\n await this.tracker.flush();\n }\n\n /**\n * Gracefully shut down the client. Flushes all pending events and\n * releases resources.\n */\n async destroy(): Promise<void> {\n await this.tracker.shutdown();\n }\n}\n\nexport { TruthClient };\n","/**\n * Versioned cache envelope + bounded eviction.\n *\n * Two concerns live here:\n *\n * 1. **Versioning.** Every cached value is wrapped in `{ v, savedAt,\n * data }`. {@link SCHEMA_VERSION} is bumped whenever the *shape* of\n * any cached query changes; readers drop entries whose `v` doesn't\n * match, so a stale on-disk mirror from an older app build can never\n * hydrate the UI with a shape the new code doesn't expect. Under\n * Path A this same constant is handed to TanStack's\n * `PersistQueryClientProvider` as the `buster`, which discards the\n * whole dehydrated cache on a version bump.\n *\n * 2. **Eviction.** A device mirror must stay bounded. {@link\n * putWithEviction} maintains an index of `key → savedAt` and evicts\n * the oldest entries once the entry count exceeds the cap.\n *\n * **Clock injection:** the pure helpers never call `Date.now()` — the\n * caller passes `now` so the eviction order and envelope timestamps are\n * deterministic under test. The provider supplies `Date.now()` at the\n * real call site.\n */\n\nimport type { OfflineStore } from \"./store\";\n\n/**\n * Bump on ANY change to a cached query's shape → invalidates old entries.\n *\n * **When to bump:** whenever the return shape of an allowlisted query\n * changes (field added/removed/renamed/retyped), or the envelope/cache\n * format here changes. **Procedure:** increment this integer by 1 in one\n * commit. That changes every `makeCacheKey` (Path B) and the TanStack\n * persist `buster` (Path A, via `provider.ts`), so on the next launch the\n * old mirror is ignored and refilled from live Convex — no migration, no\n * manual wipe. Never reuse a previous number.\n */\nexport const SCHEMA_VERSION = 1;\n\n/** Default cap on the number of cached data entries (excludes the index). */\nexport const DEFAULT_MAX_ENTRIES = 200;\n\n/** Reserved key holding the `{ key: savedAt }` eviction index. */\nexport const INDEX_KEY = \"__truth_offline_index__\";\n\nexport interface Envelope<T> {\n /** Schema version this entry was written under. */\n v: number;\n /** Epoch millis the entry was written (injected clock). */\n savedAt: number;\n /** The cached payload. */\n data: T;\n}\n\n/** Serialize `data` into a versioned envelope string. `now` is injected. */\nexport function writeEnvelope<T>(data: T, now: number): string {\n const envelope: Envelope<T> = { v: SCHEMA_VERSION, savedAt: now, data };\n return JSON.stringify(envelope);\n}\n\n/**\n * Parse a stored envelope. Returns `undefined` when the value is\n * missing, malformed, or written under a different `SCHEMA_VERSION`\n * (the version-mismatch drop). Never throws.\n */\nexport function readEnvelope<T>(\n raw: string | null | undefined,\n): { data: T; savedAt: number } | undefined {\n if (raw == null) {\n return undefined;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (typeof parsed !== \"object\" || parsed === null) {\n return undefined;\n }\n // Validate the envelope shape before trusting it: a malformed entry must\n // fail closed (drop to `undefined`) rather than hand back a non-number\n // `savedAt` or a missing `data` that breaks consumers downstream.\n const envelope = parsed as Partial<Envelope<T>>;\n if (\n envelope.v !== SCHEMA_VERSION ||\n typeof envelope.savedAt !== \"number\" ||\n !Number.isFinite(envelope.savedAt) ||\n !(\"data\" in envelope)\n ) {\n return undefined;\n }\n return { data: envelope.data as T, savedAt: envelope.savedAt };\n}\n\ntype EvictionIndex = Record<string, number>;\n\nfunction readIndex(store: OfflineStore): EvictionIndex {\n const raw = store.get(INDEX_KEY);\n if (raw == null) {\n return {};\n }\n try {\n const parsed = JSON.parse(raw);\n return typeof parsed === \"object\" && parsed !== null\n ? (parsed as EvictionIndex)\n : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Write `data` under `key` as a versioned envelope and record it in the\n * eviction index. When the index exceeds `maxEntries`, evict the oldest\n * entries (lowest `savedAt`) — never the entry just written — deleting\n * both their data and their index slot.\n */\nexport function putWithEviction<T>(\n store: OfflineStore,\n key: string,\n data: T,\n now: number,\n maxEntries: number = DEFAULT_MAX_ENTRIES,\n): void {\n store.set(key, writeEnvelope(data, now));\n\n const index = readIndex(store);\n index[key] = now;\n\n const keys = Object.keys(index);\n if (keys.length > maxEntries) {\n const overflow = keys.length - maxEntries;\n // Oldest first, never the just-written key. Excluding `key` BEFORE\n // slicing (rather than skipping it mid-loop) guarantees exactly\n // `overflow` deletions even when `now` is non-monotonic and the new\n // key sorts among the oldest — otherwise a skipped victim would\n // leave the cache one entry over the cap.\n const victims = keys\n .filter((k) => k !== key)\n .sort((a, b) => index[a] - index[b])\n .slice(0, overflow);\n for (const victim of victims) {\n store.delete(victim);\n delete index[victim];\n }\n }\n\n store.set(INDEX_KEY, JSON.stringify(index));\n}\n","/**\n * TanStack Query persister backed by the injected {@link OfflineStore}.\n *\n * Under Path A the whole dehydrated query cache is serialized as a\n * single blob and written through the synchronous `OfflineStore` (MMKV\n * on `ch/`). On cold start `PersistQueryClientProvider` calls\n * `restoreClient()` to rehydrate that blob before the Convex websocket\n * reconnects, which is what makes last-known data render with no\n * network.\n *\n * The store is synchronous, so all three methods resolve immediately —\n * but the `Persister` contract allows returning a value or a promise, so\n * returning the raw values is fine.\n */\n\nimport type {\n PersistedClient,\n Persister,\n} from \"@tanstack/react-query-persist-client\";\nimport type { OfflineStore } from \"./store\";\n\n/** Single key the dehydrated TanStack cache is stored under. */\nexport const PERSIST_CACHE_KEY = \"truth-offline:tanstack-cache\";\n\n/**\n * Minimal structural guard for a dehydrated TanStack client. We only\n * assert the fields restore actually relies on (`timestamp` number +\n * `clientState` object) — enough to reject a corrupt / foreign blob\n * before it reaches `PersistQueryClientProvider`, without coupling to\n * TanStack's internal `clientState` shape.\n */\nfunction isPersistedClient(value: unknown): value is PersistedClient {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Partial<PersistedClient>;\n return (\n typeof candidate.timestamp === \"number\" &&\n Number.isFinite(candidate.timestamp) &&\n typeof candidate.clientState === \"object\" &&\n candidate.clientState !== null\n );\n}\n\n/**\n * Create a synchronous `Persister` that reads/writes the dehydrated\n * TanStack cache through `store`. Restore tolerates absent or corrupt\n * data by returning `undefined` (TanStack then starts cold).\n */\nexport function createOfflinePersister(\n store: OfflineStore,\n cacheKey: string = PERSIST_CACHE_KEY,\n): Persister {\n return {\n persistClient(client: PersistedClient): void {\n try {\n store.set(cacheKey, JSON.stringify(client));\n } catch {\n // Best-effort mirror — a serialization failure must never crash\n // the host app. Worst case we lose offline reads this session.\n }\n },\n restoreClient(): PersistedClient | undefined {\n // `store.get` is inside the try too: an injected store can throw\n // (e.g. MMKV decryption failure), and a cold start must fail closed\n // rather than crash the host app.\n try {\n const raw = store.get(cacheKey);\n if (raw == null) {\n return undefined;\n }\n const parsed = JSON.parse(raw) as unknown;\n // Fail closed on a wrong shape, not just on parse errors: a blob\n // missing `timestamp`/`clientState` would otherwise hydrate and\n // then blow up later inside TanStack's restore in a far less\n // controlled spot. Validate the minimal contract here.\n if (!isPersistedClient(parsed)) {\n return undefined;\n }\n return parsed;\n } catch {\n return undefined;\n }\n },\n removeClient(): void {\n try {\n store.delete(cacheKey);\n } catch {\n // Best-effort cleanup; a store failure must never crash the\n // logout / cache-clear paths (same fail-closed stance as the\n // persist/restore methods above).\n }\n },\n };\n}\n\n/**\n * Read the epoch-millis timestamp the persisted cache was last written\n * (TanStack stamps `timestamp` on every persist → it tracks the most\n * recent successful sync). Returns `null` when nothing is persisted or\n * the blob is unreadable. Powers the \"Offline · updated {relative}\"\n * indicator without the app having to know the dehydrated cache format.\n */\nexport function readPersistedSavedAt(\n store: OfflineStore,\n cacheKey: string = PERSIST_CACHE_KEY,\n): number | null {\n // `store.get` inside the try as well — fail closed (null) if the\n // injected store throws, same as a parse failure.\n try {\n const raw = store.get(cacheKey);\n if (raw == null) {\n return null;\n }\n const parsed = JSON.parse(raw) as { timestamp?: number };\n return typeof parsed.timestamp === \"number\" ? parsed.timestamp : null;\n } catch {\n return null;\n }\n}\n","/**\n * Offline mirror storage seam.\n *\n * The Truth SDK is consumed by two apps with very different storage\n * stories:\n * - `ch/` (Expo / React Native) — has access to native, encrypted,\n * synchronous KV (MMKV) and wants durable offline reads.\n * - `truth/apps/web` (Next.js) — has no such store and must behave\n * exactly as it does today.\n *\n * So the SDK never imports a native module. Instead the consuming app\n * *injects* an `OfflineStore` implementation through `<TruthProvider>`.\n * When none is injected the SDK falls back to {@link NoopStore} and the\n * entire offline layer becomes a transparent pass-through.\n *\n * The interface is deliberately **synchronous**: cold-start hydration\n * has to read the last-known value during the first render, before any\n * effect or promise resolves, so async storage (AsyncStorage) is not\n * sufficient here. MMKV's `getString`/`set` are synchronous and map\n * onto this directly.\n */\n\n/** Synchronous KV mirror. Sync getters enable instant cold-start hydration. */\nexport interface OfflineStore {\n /** Return the stored string for `key`, or `null` when absent. */\n get(key: string): string | null;\n /** Write `value` under `key`, overwriting any prior value. */\n set(key: string, value: string): void;\n /** Remove a single `key`. No-op when absent. */\n delete(key: string): void;\n /**\n * Wipe every key this store owns. Called on logout / user switch so a\n * second user on the same device never sees the first user's cached\n * PHI. MUST clear durably (survive the next cold start).\n */\n clearAll(): void;\n}\n\n/**\n * Used when no store is injected (e.g. `truth/apps/web`) → the offline\n * layer is a no-op and behavior is identical to a build without it.\n */\nexport class NoopStore implements OfflineStore {\n get(): string | null {\n return null;\n }\n set(): void {}\n delete(): void {}\n clearAll(): void {}\n}\n\n/**\n * Shared singleton so every `<TruthProvider>` that omits `offlineStore`\n * resolves to the *same* Noop instance. Keeps context identity stable\n * (no needless re-renders) and lets `isNoopStore` cheaply detect the\n * \"no store injected\" case to skip persistence wiring.\n */\nexport const NOOP_STORE: OfflineStore = new NoopStore();\n\n/** True when `store` is the injected-nothing default (web / flag off). */\nexport function isNoopStore(store: OfflineStore | undefined | null): boolean {\n return store == null || store instanceof NoopStore;\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\";\nimport { useTruthSdkContext } from \"./provider\";\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 /**\n * Truth API base URL — e.g. `https://app.truth.communication-hub.com`.\n * Optional when the hook is mounted under `<TruthProvider>`: the\n * provider resolves the URL from the environment (production + uat →\n * prod Lambda; everything else → sandbox). Pass an override to point\n * at a per-PR Truth stage (e.g. `https://pr-132-app.sandbox...`).\n */\n apiBaseUrl?: string;\n /**\n * `hn_live_*` API key for the caller's application. Optional when\n * provided via `<TruthProvider apiKey={...}>` — the SDK reads it from\n * context so each hook call doesn't have to repeat the prop.\n */\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 // Fall back to TruthProvider context for apiBaseUrl + apiKey so\n // CommHub (and any other SDK consumer) doesn't have to thread the\n // REST endpoint creds through every hook call. Explicit options\n // still win for per-call overrides (e.g. PR previews).\n const sdkContext = useTruthSdkContext();\n const apiBaseUrl = options.apiBaseUrl ?? sdkContext?.apiBaseUrl ?? \"\";\n const apiKey = options.apiKey ?? sdkContext?.apiKey ?? \"\";\n\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 `${apiBaseUrl}/api/notifications/vapid-key`,\n {\n headers: {\n Accept: \"application/json\",\n \"X-API-Key\": 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 }, [apiBaseUrl, 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 `${apiBaseUrl}/api/notifications/devices/register`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": 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 // Fail fast instead of POSTing to a relative `/api/...` on the host\n // app when creds are unresolved (no <TruthProvider> / no options).\n if (!apiBaseUrl || !apiKey) {\n return { ok: false, reason: \"missing_truth_config\" };\n }\n\n const res = await fetch(\n `${apiBaseUrl}/api/notifications/devices/register`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": 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 apiBaseUrl,\n 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(`${apiBaseUrl}/api/notifications/devices/unregister`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify({ nativeToken: devicePushToken }),\n }).catch(() => {\n /* non-fatal */\n });\n setDevicePushToken(null);\n }, [apiBaseUrl, 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\n/**\n * useNotificationsActions — context-aware factory for the REST-backed\n * notification actions exposed by `NotificationsResource` (send,\n * schedule, get/update preferences). Reads `apiBaseUrl` + `apiKey`\n * from `<TruthProvider>` so call sites don't have to instantiate the\n * resource with creds inline.\n *\n * Returns memoized callbacks so they're stable across re-renders.\n */\nexport interface UseNotificationsActions {\n send: (input: {\n userId: string;\n title: string;\n body: string;\n data?: Record<string, unknown>;\n badge?: number;\n sound?: string;\n }) => Promise<{\n delivered: number;\n failed?: number;\n suppressed?: boolean;\n suppressionReason?: string;\n }>;\n schedule: (input: {\n userId: string;\n title: string;\n body: string;\n scheduledAt: string;\n data?: Record<string, unknown>;\n }) => Promise<{ jobId: string }>;\n getPreferences: (userId: string) => Promise<Record<string, unknown>>;\n updatePreferences: (\n userId: string,\n prefs: Record<string, unknown>,\n ) => Promise<Record<string, unknown>>;\n}\n\nexport function useNotificationsActions(): UseNotificationsActions {\n const sdkContext = useTruthSdkContext();\n const apiBaseUrl = sdkContext?.apiBaseUrl ?? \"\";\n const apiKey = sdkContext?.apiKey ?? \"\";\n\n const post = useCallback(\n async <T>(path: string, body: unknown): Promise<T> => {\n if (!apiBaseUrl || !apiKey) {\n throw new Error(\n \"useNotificationsActions: missing apiBaseUrl/apiKey — wrap in <TruthProvider> or pass options\",\n );\n }\n const res = await fetch(`${apiBaseUrl}/api${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `Truth API ${path} failed (${res.status}): ${text.slice(0, 200)}`,\n );\n }\n return (await res.json()) as T;\n },\n [apiBaseUrl, apiKey],\n );\n\n const get = useCallback(\n async <T>(path: string): Promise<T> => {\n const res = await fetch(`${apiBaseUrl}/api${path}`, {\n method: \"GET\",\n headers: { Accept: \"application/json\", \"X-API-Key\": apiKey },\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\n `Truth API ${path} failed (${res.status}): ${text.slice(0, 200)}`,\n );\n }\n return (await res.json()) as T;\n },\n [apiBaseUrl, apiKey],\n );\n\n const send = useCallback(\n (\n input: Parameters<UseNotificationsActions[\"send\"]>[0],\n ): ReturnType<UseNotificationsActions[\"send\"]> =>\n post(\"/notifications/send\", input),\n [post],\n );\n\n const schedule = useCallback(\n (\n input: Parameters<UseNotificationsActions[\"schedule\"]>[0],\n ): ReturnType<UseNotificationsActions[\"schedule\"]> =>\n post(\"/notifications/schedule\", input),\n [post],\n );\n\n const getPreferences = useCallback(\n (userId: string) =>\n get<Record<string, unknown>>(\n `/notifications/preferences/${encodeURIComponent(userId)}`,\n ),\n [get],\n );\n\n const updatePreferences = useCallback(\n (userId: string, prefs: Record<string, unknown>) =>\n post<Record<string, unknown>>(\n `/notifications/preferences/${encodeURIComponent(userId)}`,\n prefs,\n ),\n [post],\n );\n\n return { send, schedule, getPreferences, updatePreferences };\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 * 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 type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist: family members shown in the patient panel must\n// render from the mirror offline. Aliased so the call site below is unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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 = !!(\n input?.phoneNumbers && input.phoneNumbers.length > 0\n );\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 type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist: previously-run patient searches re-render from the\n// mirror offline. Aliased so the call site below is unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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 /** Middle name — surfaced so the picker renders the full legal name (B9). */\n middleName: string | undefined;\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 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// Offline-reads allowlist: inbox card names / office / EHR ids must render\n// from the mirror offline. Aliased so the call sites below are unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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) {\n return undefined;\n }\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) {\n return undefined;\n }\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 * 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 type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { Reminder } from \"../types/reminder\";\nimport type { UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist (Phase 2.1): the inbox reminder clock is part\n// of the durable mirror. Aliased so the call site below is unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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 hooks for conversation tasks (My Tasks surface).\n *\n * Provides a reactive `useMutation` wrapper around the\n * `conversationTasks:markSeen` Convex mutation so CommHub can replace\n * `useMark_Event_Activity_SeenMutation` from `@/generated/graphql`\n * with a Truth SDK equivalent without touching `react.ts` exports (the\n * central agent wires those).\n *\n * Pattern mirrors the existing hooks in `./conversations.ts`:\n * - `makeFunctionReference` for decoupled Convex references\n * - SKIP sentinel for conditional execution\n * - Returns a stable callback, not a live subscription\n *\n * Must be used within `<TruthProvider />`.\n */\n\n\"use client\";\n\nimport { useMutation } from \"convex/react\";\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport { useCallback } from \"react\";\n\n// ---------------------------------------------------------------------------\n// Convex mutation reference — decoupled from the consuming app's codegen\n// ---------------------------------------------------------------------------\n\nconst conversationTasksMarkSeenRef = makeFunctionReference<\n \"mutation\",\n { taskId: string; userId: string },\n { ok: true }\n>(\"conversationTasks:markSeen\");\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a stable `markSeen(taskId, userId)` callback backed by the\n * `conversationTasks:markSeen` Convex mutation. Replaces the Hasura\n * `useMark_Event_Activity_SeenMutation` in CommHub's My Tasks surface.\n *\n * @example\n * ```tsx\n * const markSeen = useConversationTaskMarkSeen();\n *\n * // When a task row is pressed:\n * if (!task.seenBy.includes(userId)) {\n * markSeen(task.id, userId).catch(console.error);\n * }\n * ```\n */\nfunction useConversationTaskMarkSeen(): (\n taskId: string,\n userId: string,\n) => Promise<{ ok: true }> {\n const mutate = useMutation(\n conversationTasksMarkSeenRef as FunctionReference<\"mutation\">,\n );\n\n return useCallback(\n (taskId: string, userId: string) =>\n mutate({ taskId, userId }) as Promise<{ ok: true }>,\n [mutate],\n );\n}\n\nexport { useConversationTaskMarkSeen };\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 // Fall back to `EXPO_PUBLIC_TRUTH_API_KEY` when the prop isn't passed\n // so consumers can mount this provider with just `environment` set.\n const resolvedApiKey =\n apiKey ??\n (typeof process !== \"undefined\"\n ? process.env?.EXPO_PUBLIC_TRUTH_API_KEY\n : undefined) ??\n \"\";\n const value = useMemo<TruthTrackingContextValue>(() => {\n const tracker = new Tracker({\n apiKey: resolvedApiKey,\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 }, [resolvedApiKey, 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 * 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 type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\nimport type { UserSettings } from \"../resources/user-settings\";\nimport type { UseQueryResult } from \"./conversations\";\n// Offline-reads allowlist: user settings render from the mirror offline.\n// Aliased so the call site below is unchanged.\nimport { usePersistentQuery as useQuery } from \"./offline/use-persistent-query\";\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 * useUserSync — mirror the caller's Clerk profile into Truth's user roster.\n *\n * Truth's `users` Convex table backs the SMS push fan-out (office cohort\n * + `notifications_enabled`), assignee pickers, and office-scoped\n * filters. CommHub mounts this hook on app start so the active user\n * lands in Truth without anyone reaching into Hasura.\n *\n * Resolves `apiBaseUrl` + `apiKey` from `<TruthProvider>` context, so\n * the call site only passes the per-user details:\n *\n * ```tsx\n * useUserSync({ userId, email, name, imageUrl });\n * ```\n *\n * Idempotent — fires once per (userId, email, name, imageUrl) change.\n */\n\n\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useTruthSdkContext } from \"./provider\";\n\nexport interface UseUserSyncInput {\n /** Truth API base URL. Defaults to TruthProvider context. */\n apiBaseUrl?: string;\n /** `hn_live_*` application key. Defaults to TruthProvider context. */\n apiKey?: string;\n /** Clerk userId. The hook is a no-op while this is nullish. */\n userId: string | null | undefined;\n email?: string;\n name?: string;\n firstName?: string;\n lastName?: string;\n imageUrl?: string;\n notificationsEnabled?: boolean;\n}\n\nexport interface UseUserSyncResult {\n /** \"idle\" before any sync, \"syncing\" mid-fetch, \"synced\" on success, \"error\" otherwise. */\n status: \"idle\" | \"syncing\" | \"synced\" | \"error\";\n /** Last error message when status === \"error\". */\n error: string | null;\n /** Manually re-run the sync. Usually not needed — the hook auto-syncs. */\n sync: () => Promise<{ ok: boolean; reason?: string }>;\n}\n\nexport function useUserSync(input: UseUserSyncInput): UseUserSyncResult {\n const sdkContext = useTruthSdkContext();\n const apiBaseUrl = input.apiBaseUrl ?? sdkContext?.apiBaseUrl ?? \"\";\n const apiKey = input.apiKey ?? sdkContext?.apiKey ?? \"\";\n\n const [status, setStatus] = useState<UseUserSyncResult[\"status\"]>(\"idle\");\n const [error, setError] = useState<string | null>(null);\n // Track the last-synced payload so we don't fire on every re-render.\n const lastKeyRef = useRef<string | null>(null);\n\n const sync = async (): Promise<{ ok: boolean; reason?: string }> => {\n if (!input.userId) {\n return { ok: false, reason: \"missing_userId\" };\n }\n if (!apiBaseUrl || !apiKey) {\n return { ok: false, reason: \"missing_truth_config\" };\n }\n setStatus(\"syncing\");\n setError(null);\n try {\n const res = await fetch(`${apiBaseUrl}/api/users/sync`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": apiKey,\n },\n body: JSON.stringify({\n userId: input.userId,\n email: input.email,\n name: input.name,\n firstName: input.firstName,\n lastName: input.lastName,\n imageUrl: input.imageUrl,\n notificationsEnabled: input.notificationsEnabled,\n }),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n const reason = `sync_failed_${res.status}: ${text.slice(0, 120)}`;\n setStatus(\"error\");\n setError(reason);\n return { ok: false, reason };\n }\n setStatus(\"synced\");\n return { ok: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n setStatus(\"error\");\n setError(message);\n return { ok: false, reason: message };\n }\n };\n\n useEffect(() => {\n if (!input.userId) {\n return;\n }\n const key = [\n // Include the resolved config so a first mount with missing creds\n // (sync no-ops as \"missing_truth_config\") doesn't pin the key and\n // permanently skip the valid retry once creds arrive.\n apiBaseUrl,\n apiKey,\n input.userId,\n input.email ?? \"\",\n input.name ?? \"\",\n input.firstName ?? \"\",\n input.lastName ?? \"\",\n input.imageUrl ?? \"\",\n input.notificationsEnabled === undefined\n ? \"\"\n : String(input.notificationsEnabled),\n ].join(\"|\");\n if (key === lastKeyRef.current) {\n return;\n }\n lastKeyRef.current = key;\n void sync();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n apiBaseUrl,\n apiKey,\n input.userId,\n input.email,\n input.name,\n input.firstName,\n input.lastName,\n input.imageUrl,\n input.notificationsEnabled,\n ]);\n\n return { status, error, sync };\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) {\n return null;\n }\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":["CONVEX_URLS","import_react_query","useTanstackQuery","useLiveQuery","import_server","import_server","import_react","SKIP","toResult","import_react","import_react_query","import_react","import_server","CONVEX_URLS","_a","_b","import_server","import_react","_a","_b","res","import_server","SKIP","import_server","SKIP","import_server","import_react","SKIP","import_server","SKIP","import_react","import_server","import_react","import_server","SKIP","import_react","_a","_b","_c","_d","import_react"]}
|