@ada-cx/messaging-bridge 1.5.1 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import type { CustomAppFrameStyles } from "../shared-utils/index.js";
1
2
  import type { AppDisplayState, AppEvents, CsatSubmitData, Message, OptionItem, QuickReply } from "./types.js";
2
3
  /**
3
4
  * The minimal client surface the operations layer builds on. Satisfied by the
@@ -186,6 +187,12 @@ export interface BridgeOperations {
186
187
  /**
187
188
  * Request an older page of history. No-op unless `chat.canLoadMore` is
188
189
  * true and no load is already in flight.
190
+ * Sends `chat.logs.loadMore`. After a failed page, Core ignores requests
191
+ * during a delay for that conversation without setting `chat.loadingPreviousMessages`.
192
+ * The delay starts at 1 second and doubles after each failure, up to 30 seconds.
193
+ * Retry on a new user scroll, not when the loading flag clears.
194
+ * When optional `chat.previousMessagesLoadSeq` rises, you can request the next page.
195
+ * It counts successful older-history pages, including pages with no visible rows.
189
196
  */
190
197
  loadOlderMessages(): void;
191
198
  /**
@@ -340,6 +347,8 @@ export interface BridgeOperations {
340
347
  dismissError(): void;
341
348
  /** Ask the SDK host to expand or restore the app's viewport surface. */
342
349
  setHostViewportExpanded(expanded: boolean): void;
350
+ /** Replace floating custom-app frame styles. Pass {} to restore defaults. */
351
+ setFrameStyles(styles: CustomAppFrameStyles): void;
343
352
  /** Retry after a response outage banner. */
344
353
  retryAfterOutage(): void;
345
354
  /**
@@ -1,4 +1,4 @@
1
- import type { CsatFeedbackOption, CsatSurveySettings, FileUploadErrorData } from "../shared-utils/index.js";
1
+ import type { CsatFeedbackOption, CsatSurveySettings, CustomAppFrameStyles, FileUploadErrorData } from "../shared-utils/index.js";
2
2
  /**
3
3
  * Bridge contract definitions describing the state and events that flow between
4
4
  * the messaging core frame and the display layer (app or native WebView).
@@ -378,6 +378,10 @@ export interface AppDisplayState {
378
378
  "config.showAllQuickReplies": boolean;
379
379
  "config.isMobileViewport": boolean;
380
380
  "appearance.userTheme": "light" | "dark" | "auto" | null;
381
+ "appearance.hostViewport": {
382
+ width: number;
383
+ height: number;
384
+ } | null;
381
385
  "appearance.userTextSize": "small" | "default" | "large" | null;
382
386
  "config.fallbackUi": FallbackUiConfig | null;
383
387
  "config.buttonSize": number;
@@ -436,6 +440,8 @@ export interface AppDisplayState {
436
440
  "chat.isConversationActive"?: boolean;
437
441
  "chat.canLoadMore": boolean;
438
442
  "chat.loadingPreviousMessages": boolean;
443
+ /** Counts successful older-history pages, including pages with no visible rows. */
444
+ "chat.previousMessagesLoadSeq"?: number;
439
445
  /** Bumped each time restored/baseline history is (re)loaded; drives the
440
446
  * app's one-shot "land at the bottom of restored history" scroll on reload. */
441
447
  "chat.recentMessagesLoadSeq": number;
@@ -766,6 +772,9 @@ export interface AppEvents {
766
772
  "ui.hostViewport.set": {
767
773
  expanded: boolean;
768
774
  };
775
+ "ui.frameStyles.set": {
776
+ styles: CustomAppFrameStyles;
777
+ };
769
778
  "liveAgent.handoff.request": {
770
779
  platformData: {
771
780
  platform: string;
@@ -6,7 +6,7 @@
6
6
  * `loadMessagingBridge()` so Ada can patch security-bearing logic without a
7
7
  * customer release. Never add runtime implementation to this entry.
8
8
  */
9
- export type { FileUploadErrorData, FileUploadErrorReason, } from "../shared-utils/index.js";
9
+ export type { CustomAppFrameStyles, FileUploadErrorData, FileUploadErrorReason, } from "../shared-utils/index.js";
10
10
  export type { BridgeClient } from "../contract/bridge-client.js";
11
11
  export type { MessageGroupingEntry, UnreadSelection } from "../contract/derive.js";
12
12
  export type { BridgeClientDestroyedError, BridgeOperations, BridgeOperationsHost, CaptureHandle, CsatSubmitHandle, EndChatEligibilityResult, FileUploadHandle, ObserveOptions, OperationResult, SendHandle, SettleOptions, } from "../contract/operations.js";
@@ -393,6 +393,9 @@ function d(e) {
393
393
  setHostViewportExpanded(e) {
394
394
  n.sendEvent("ui.hostViewport.set", { expanded: e });
395
395
  },
396
+ setFrameStyles(e) {
397
+ n.sendEvent("ui.frameStyles.set", { styles: e });
398
+ },
396
399
  retryAfterOutage() {
397
400
  n.sendEvent("outage.retry");
398
401
  },
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","names":[],"sources":["../../../../shared/utils/src/browser-polyfills.ts","../../src/contract/operations.ts","../../src/npm/testing.ts"],"sourcesContent":["/**\n * Defence in depth for engines just below the declared browser floor.\n * The floor is defined in `messaging/vite.browser-targets.mjs`.\n *\n * Internal Ada code should prefer direct helpers, such as `hasOwn(...)` from\n * `guards.ts`. Every Ada bundle includes these fallbacks, including the host-page\n * SDK. These fallbacks also ship inside the customer-held npm tarballs of the SDK\n * and bridge. Each fallback defines a member only when it is missing. These\n * fallbacks do not extend browser support below the floor required by the shipped CSS.\n */\n\nconst objectHasOwnProperty = Object.prototype.hasOwnProperty;\n\nfunction defineValue(\n\ttarget: object,\n\tname: PropertyKey,\n\tvalue: (...args: unknown[]) => unknown,\n): void {\n\tObject.defineProperty(target, name, {\n\t\tvalue,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t});\n}\n\nif (!Object.hasOwn) {\n\tdefineValue(Object, \"hasOwn\", (object: unknown, key: unknown) => {\n\t\tif (object == null) {\n\t\t\tthrow new TypeError(\n\t\t\t\t\"ada browser-polyfills: Cannot convert undefined or null to object\",\n\t\t\t);\n\t\t}\n\t\treturn objectHasOwnProperty.call(Object(object), key as PropertyKey);\n\t});\n}\n\nfunction at<T>(\n\tthis: { length: number; [index: number]: T },\n\tindex: unknown,\n): T | undefined {\n\tconst length = Math.trunc(Number(this.length)) || 0;\n\tlet relativeIndex = Math.trunc(Number(index)) || 0;\n\tif (relativeIndex < 0) {\n\t\trelativeIndex += length;\n\t}\n\treturn relativeIndex < 0 || relativeIndex >= length\n\t\t? undefined\n\t\t: this[relativeIndex];\n}\n\nfunction stringAt(this: string, index: unknown): string | undefined {\n\tconst value = String(this);\n\tconst length = value.length;\n\tlet relativeIndex = Math.trunc(Number(index)) || 0;\n\tif (relativeIndex < 0) {\n\t\trelativeIndex += length;\n\t}\n\treturn relativeIndex < 0 || relativeIndex >= length\n\t\t? undefined\n\t\t: value.charAt(relativeIndex);\n}\n\nconst typedArrayConstructors = [\n\tglobalThis.Int8Array,\n\tglobalThis.Uint8Array,\n\tglobalThis.Uint8ClampedArray,\n\tglobalThis.Int16Array,\n\tglobalThis.Uint16Array,\n\tglobalThis.Int32Array,\n\tglobalThis.Uint32Array,\n\tglobalThis.Float32Array,\n\tglobalThis.Float64Array,\n\tglobalThis.BigInt64Array,\n\tglobalThis.BigUint64Array,\n].filter(Boolean);\n\nif (!Array.prototype.at) {\n\tdefineValue(Array.prototype, \"at\", at);\n}\n\nif (!String.prototype.at) {\n\tdefineValue(String.prototype, \"at\", stringAt);\n}\n\nfor (const TypedArrayConstructor of typedArrayConstructors) {\n\tconst prototype = TypedArrayConstructor.prototype;\n\tif (!prototype.at) {\n\t\tdefineValue(prototype, \"at\", at);\n\t}\n}\n","import type {\n\tAppDisplayState,\n\tAppEvents,\n\tCsatSubmitData,\n\tMessage,\n\tOptionItem,\n\tQuickReply,\n} from \"./types\";\n\n/**\n * `client.operations` — typed helpers over the raw event/state contract,\n * mined from Ada's reference app so custom apps inherit its validated\n * behavior (debounces, guards, correlation) instead of reimplementing it.\n *\n * Implementation note: the npm test mock and CDN runtime share this module.\n * The mock reuses it over its own in-memory host so mocked handles resolve\n * from scripted state, but the published tarball never carries transport\n * logic — every helper here speaks through `sendEvent`/`subscribe` alone.\n */\n\n/** Debounce window for {@link BridgeOperations.markRead} reports. */\nconst READ_REPORT_DEBOUNCE_MS = 400;\n/** Default {@link BridgeOperations.startNewConversation} cooldown. */\nconst START_NEW_CONVERSATION_COOLDOWN_MS = 5_000;\n\n/**\n * The minimal client surface the operations layer builds on. Satisfied by the\n * real `BridgeClient` and by the npm test mock.\n */\nexport interface BridgeOperationsHost {\n\tgetState(): AppDisplayState | null;\n\tsubscribe(callback: () => void): () => void;\n\tsendEvent<K extends keyof AppEvents>(\n\t\tevent: K,\n\t\t...args: AppEvents[K] extends undefined ? [] : [payload: AppEvents[K]]\n\t): void;\n}\n\n/** Outcome of a correlated submit: accepted, or rejected with a message. */\nexport type OperationResult = { ok: true } | { ok: false; message: string };\n\nexport interface SettleOptions {\n\t/** Reject with an `Error` if the operation has not settled in time. */\n\ttimeoutMs?: number;\n}\n\n/**\n * Rejection every in-flight `settled()` promise receives when the client is\n * destroyed, so teardown is the layer's bound of last resort even without a\n * `timeoutMs`. The class identity does not survive the npm/CDN module split,\n * so detect it by `error.name === \"BridgeClientDestroyedError\"` (or via\n * {@link BridgeClientDestroyedError.is}), never by `instanceof` alone.\n */\nexport class BridgeClientDestroyedError extends Error {\n\tconstructor(message = \"The bridge client was destroyed.\") {\n\t\tsuper(message);\n\t\tthis.name = \"BridgeClientDestroyedError\";\n\t}\n\n\tstatic is(error: unknown): error is BridgeClientDestroyedError {\n\t\treturn (\n\t\t\terror instanceof Error && error.name === \"BridgeClientDestroyedError\"\n\t\t);\n\t}\n}\n\nexport interface SendHandle {\n\t/**\n\t * The uuid put on the wire as `tempMessageUuid`. Core seeds the optimistic\n\t * message's `clientKey` from it, so the sent message is findable in\n\t * `chat.messages` by `clientKey === tempMessageUuid`.\n\t */\n\treadonly tempMessageUuid: string;\n\t/**\n\t * Resolves with the message once it appears in `chat.messages` (the\n\t * optimistic row; the durable server echo keeps the same `clientKey`).\n\t * Rejects when core reports THIS send's error before the row appears —\n\t * rate limit, over-length, session not ready, dispatch failure —\n\t * correlated exactly on `chat.error.clientKey` matching the minted\n\t * `tempMessageUuid`, so an unrelated failure in the same window never\n\t * falsely rejects a delivered send. On a core document that predates\n\t * `chat.error.clientKey`, falls back to the positional\n\t * `chat.error.seq`-advance detection, and before that to `chat.error`\n\t * text transitions. A secret send NEVER resolves — core deliberately\n\t * writes no transcript row for it — so pass `timeoutMs` or skip\n\t * `settled()` for secret sends. Destroying the client rejects every\n\t * pending `settled()` with a {@link BridgeClientDestroyedError}.\n\t */\n\tsettled(opts?: SettleOptions): Promise<Message>;\n}\n\nexport interface CaptureHandle {\n\t/**\n\t * Resolves on the verdict for THIS capture submit: `{ ok: true }` when the\n\t * value was accepted (core removes the capture block), or\n\t * `{ ok: false, message }` when it was rejected — a server validation\n\t * rejection (observed on the `isSubmitting` falling edge, with the\n\t * validation error text), or a failure/drop core stamps on the capture's\n\t * `lastSubmitFailure` keyed to this submit's own minted `submitId` (no\n\t * session to round-trip, value over the length cap, transport failure, a\n\t * duplicate submit while one is in flight, a host-driven reset). The\n\t * correlation is exact: the handle never reads the global `chat.error`,\n\t * so an unrelated failure in the same window can never masquerade as this\n\t * capture's verdict. On a core document that predates `lastSubmitFailure`,\n\t * those failures are not capture-attributable at all and the handle stays\n\t * pending — pass `timeoutMs` when targeting older cores.\n\t */\n\tsettled(opts?: SettleOptions): Promise<OperationResult>;\n}\n\nexport interface CsatSubmitHandle {\n\t/**\n\t * Resolves when core reports this survey's outcome, correlated on the\n\t * success seq advance x surveyType x conversationId triple (and the\n\t * mirror-image error triple). A `partial` submit reports no terminal\n\t * outcome, so `settled()` on one only settles via `timeoutMs`.\n\t */\n\tsettled(opts?: SettleOptions): Promise<OperationResult>;\n}\n\nexport interface FileUploadHandle {\n\t/** The minted upload id sent with `file.upload.start`. */\n\treadonly uploadId: string;\n\t/**\n\t * Re-send the EXACT same `{ file, uploadId }` pair. Core correlates the\n\t * retry to the failed attempt by that pair, so a fresh pair would orphan\n\t * the original error state.\n\t */\n\tretry(): void;\n\t/** Dismiss a reported upload error (`file.uploadError`). */\n\tdismissError(): void;\n}\n\nexport interface EndChatEligibilityResult {\n\t/** `csat.endChatEligible` at the moment the answer arrived. */\n\teligible: boolean | null;\n\t/** Which survey End Chat should present, when eligible. */\n\tsurveyTarget: \"bot\" | \"agent\" | null;\n}\n\nexport interface ObserveOptions<T> {\n\t/** Change detector; defaults to `Object.is`. */\n\tequals?: (a: T, b: T) => boolean;\n\t/** Also invoke the callback immediately with the current value. */\n\temitInitial?: boolean;\n}\n\n/**\n * Typed helpers over the bridge event/state contract. Obtain via\n * `client.operations`. Fire-and-forget helpers return `void`; correlated\n * helpers return a handle or Promise that settles from state transitions\n * (there is no request/response channel — core answers through state).\n */\nexport interface BridgeOperations {\n\t/**\n\t * Send a chat message. The body is trimmed; an empty result sends nothing\n\t * (the returned handle then rejects). `secret: true` sends a masked\n\t * message: the raw body never enters the transcript and the handle never\n\t * resolves. While `ui.secretMessage.isOpen` is true you likely want\n\t * `secret: true` to match the composer's mode.\n\t */\n\tsendMessage(body: string, opts?: { secret?: boolean }): SendHandle;\n\t/** Retry a failed send by message id (see {@link canRetryMessage}). */\n\tretryMessage(messageId: string): void;\n\t/**\n\t * Whether the retry affordance should show for a message: a `text`\n\t * message core marked `isRetryable`, no disconnected Zendesk SDK, and the\n\t * message belongs to the current conversation (or predates stamping).\n\t */\n\tcanRetryMessage(message: Message): boolean;\n\n\t/** Answer a quick-reply chip. Pass the chip and its message id verbatim. */\n\tselectQuickReply(reply: QuickReply, messageId: string): void;\n\t/** Answer an options block. The label falls back to the option id. */\n\tselectOption(option: OptionItem, messageId: string): void;\n\t/**\n\t * Submit the active selectable list (`conversation.stateData.listSelection`\n\t * supplies the `dataId`). No-op when no list selection is active.\n\t */\n\tsubmitListSelection(selectedIds: string[]): void;\n\t/**\n\t * Cancel the active selectable list. Uses the block's own cancel response\n\t * and label (`label` overrides the visible label). No-op when no list\n\t * selection is active or it has no cancel response.\n\t */\n\tcancelListSelection(label?: string): void;\n\t/**\n\t * Submit the active capture field (`conversation.stateData.capture`\n\t * supplies the `dataId`). Each call mints its own `submitId`, so the\n\t * handle settles only on ITS OWN submit's verdict or failure — a second\n\t * submit sent while one is in flight is dropped by core and its handle\n\t * resolves `{ ok: false }` on its own terms rather than inheriting the\n\t * in-flight submit's verdict.\n\t */\n\tsubmitCapture(value: string): CaptureHandle;\n\t/**\n\t * Cancel the active capture field. `label` overrides the block's own\n\t * cancel label. No-op when no capture is active or it has no cancel\n\t * response.\n\t */\n\tcancelCapture(label?: string): void;\n\n\t/** React to a bot message with a thumbs vote. */\n\taddReaction(messageId: string, reaction: 1 | -1): void;\n\t/**\n\t * Report that the user activated a `link` message. Core resolves the\n\t * stored message for tracking and, with `navigateInHost`, validated\n\t * same-tab host navigation.\n\t */\n\treportLinkClick(messageId: string, opts?: { navigateInHost?: boolean }): void;\n\n\t/**\n\t * Advance the persisted read watermark. Reports are debounced 400 ms, the\n\t * watermark is a monotonic lexical max over `_id` cursor strings (calling\n\t * with an older cursor never lowers it), and the accumulator resets when\n\t * `chat.conversationId` changes so a pending cursor from conversation A\n\t * can never commit against B. Call with `message.cursor` for each message\n\t * that scrolls into view; messages stamped with a DIFFERENT\n\t * `conversationId` must be excluded by the caller (the transcript\n\t * preserves the previous conversation's rows).\n\t */\n\tmarkRead(cursor: string, opts?: { conversationId?: string }): void;\n\n\t/**\n\t * Request an older page of history. No-op unless `chat.canLoadMore` is\n\t * true and no load is already in flight.\n\t */\n\tloadOlderMessages(): void;\n\t/**\n\t * Invoke `callback` each time restored/baseline history (re)loads\n\t * (`chat.recentMessagesLoadSeq` advances) — the one-shot trigger for\n\t * \"scroll to the bottom of restored history\" and for seeding an unread\n\t * divider. Returns an unsubscribe function.\n\t */\n\tonHistoryLoaded(callback: (seq: number) => void): () => void;\n\n\t/**\n\t * Submit a CSAT survey. Pass `conversationId: message.conversationId` for\n\t * a survey rendered IN the transcript (the row can outlive its\n\t * conversation); omit it — or pass `null`, which is treated as omitted on\n\t * the wire and in correlation — for End Chat / proactive flows, where\n\t * core resolves the conversation itself.\n\t */\n\tsubmitCsat(\n\t\tdata: CsatSubmitData,\n\t\topts: {\n\t\t\tsurveyType: string;\n\t\t\tpartial?: boolean;\n\t\t\tconversationId?: string | null;\n\t\t},\n\t): CsatSubmitHandle;\n\t/**\n\t * Analytics: report that a survey was displayed. Fire-and-forget. Core drops\n\t * it for post-agent survey types (`live_chat`): api records that survey as\n\t * shown when it routes its row into the transcript, so a client record would\n\t * count the same prompt twice.\n\t */\n\ttrackCsatShown(surveyType: string, conversationId?: string | null): void;\n\n\t/**\n\t * Ask core whether End Chat should present a survey. Correlated by\n\t * latching `csat.endChatEligibility.seq` and resolving when it advances\n\t * (there is no request id; a `ready -> ready` re-check changes no other\n\t * observable state).\n\t */\n\tcheckEndChatEligibility(\n\t\topts?: SettleOptions,\n\t): Promise<EndChatEligibilityResult>;\n\t/**\n\t * End the conversation now with no survey. On a bot with\n\t * `config.features.endLiveChat` this is also the End Chat that follows a live\n\t * chat (`liveAgent.liveChatEndedInConversation` is true): call it straight\n\t * from the End Chat control — no confirmation, no `checkEndChatEligibility()`,\n\t * no survey — as legacy `END_LIVE_CHAT` does. While such a bot is still in a\n\t * live chat, call `endLiveChat()` instead.\n\t */\n\tskipCsatAndEndChat(): void;\n\t/**\n\t * End only the live-agent leg (legacy `END_LIVE_CHAT`). On a bot with\n\t * `config.features.endLiveChat`, an End Chat confirmed while\n\t * `liveAgent.inLiveChat` MUST use this rather than `skipCsatAndEndChat()` or\n\t * an `end_chat` `submitCsat()`, and offer no end-chat survey: the conversation\n\t * stays open and api routes the post-agent (`live_chat`) survey as an inline\n\t * row once the leg has ended, whereas an end-chat submission there is stored\n\t * as the agent's rating under the wrong survey type. Core ignores it outside\n\t * a live-agent leg.\n\t */\n\tendLiveChat(): void;\n\t/**\n\t * Start a new conversation while preserving prior messages. Applies a local\n\t * cooldown (default 5 s); `false` means no request was sent, while `true`\n\t * means only that it was sent. Also returns false while\n\t * `app.conversationStartPending` is true, without extending the cooldown.\n\t * Proactive requests follow resetApp's shared restart behavior below.\n\t */\n\tstartNewConversation(opts?: { cooldownMs?: number }): boolean;\n\t/**\n\t * Clear the transcript and start a new conversation. If proactive requests\n\t * are already pending, this and startNewConversation wait for them: success\n\t * completes the restart with the proactive's messages and existing transcript\n\t * intact, without an extra greeting. A completed success awaiting conversation\n\t * delivery can satisfy one restart too; another explicit restart after that\n\t * completion is a new intent, allowing recovery when no conversation arrives.\n\t * If all fail and no newer conversation,\n\t * session or accepted user send has superseded the request, the latest action\n\t * accepted by Core runs once: resetApp clears history; startNewConversation preserves it. Render\n\t * both controls busy while `app.conversationStartPending` is true; it is\n\t * optional on older cores. This void helper still sends while pending.\n\t * An accepted user send or retry cancels the waiting restart; a rejected send\n\t * does not.\n\t * Without pending work or an unused success awaiting delivery, resets begin immediately.\n\t */\n\tresetApp(): void;\n\n\t/**\n\t * Report a content-free composer-changed edge (drives live-agent typing\n\t * indicators). The event is payload-free by contract — composer text must\n\t * never cross the frame boundary — and this helper suppresses it entirely\n\t * in secret mode (pass `secret: true`, or leave it to the\n\t * `ui.secretMessage.isOpen` state check): even a content-free typing edge\n\t * is a signal about the secret being typed.\n\t */\n\tnotifyComposerChanged(opts?: { secret?: boolean }): void;\n\t/** Open or close the masked (secret) composer mode. */\n\ttoggleSecretMessage(open: boolean): void;\n\n\t/**\n\t * Start a live-agent file upload. Mints and retains the `{ file,\n\t * uploadId }` pair so `retry()` replays exactly it. Progress and errors\n\t * land on `file.isUploading`, `file.transfer`, and `file.uploadError`\n\t * (core can report an error without ever setting `isUploading`).\n\t */\n\tstartFileUpload(file: File): FileUploadHandle;\n\n\t/** Change the conversation language (BCP 47 tag the bot offers). */\n\tsetLanguage(language: string): void;\n\t/**\n\t * Email the transcript. Resolves/rejects when `transcript.email.status`\n\t * reaches `success | error` with its `.seq` advanced past this call's\n\t * baseline — a request core drops without ever broadcasting `pending`\n\t * (a host-driven reset in progress) still rejects. On a core document\n\t * that predates the seq, falls back to the `pending -> terminal` edge.\n\t * The email is trimmed; an empty result rejects immediately without\n\t * sending (core drops an empty email silently, so nothing would ever\n\t * settle it). The request is single-flight and address-aware: a call for\n\t * the SAME address as an in-flight request joins it and settles on its\n\t * verdict; a call for a DIFFERENT address is rejected address-matched on\n\t * `transcript.email.conflict.*` — ONLY the conflicting call's promise\n\t * rejects, while the in-flight request still completes, writes the final\n\t * status, and settles its own handle with the true outcome (a rejection\n\t * here never means the in-flight email was not sent). On a core document\n\t * that predates the conflict channel, a conflicting call settles on the\n\t * in-flight verdict instead.\n\t */\n\temailTranscript(email: string, opts?: SettleOptions): Promise<void>;\n\t/**\n\t * Download the transcript. Same settle contract as\n\t * {@link BridgeOperations.emailTranscript}, on\n\t * `transcript.download.status`.\n\t */\n\tdownloadTranscript(opts?: SettleOptions): Promise<void>;\n\t/** Toggle the new-message alert sound. */\n\tsetAlertSound(enabled: boolean): void;\n\t/**\n\t * Ask the HOST page to request Web Notification permission (permission is\n\t * per-origin, so the widget frame's own value is not the governing one).\n\t * Result lands on `chatter.notificationPermission`.\n\t */\n\trequestNotificationPermission(): void;\n\t/** Set the user's in-widget theme override. */\n\tsetTheme(theme: \"light\" | \"dark\" | \"auto\"): void;\n\t/** Set the user's in-widget text-size override. */\n\tsetTextSize(size: \"small\" | \"default\" | \"large\"): void;\n\n\t/** The header close affordance (ends the chat when `endChat.canEnd`). */\n\tclose(): void;\n\t/** Minimize the widget. */\n\tminimize(): void;\n\t/** Dismiss the visible toast. */\n\tdismissToast(): void;\n\t/**\n\t * Clear `chat.error` in core. Clear core state rather than hiding the\n\t * error locally, or the session's second error renders nothing.\n\t */\n\tdismissError(): void;\n\t/** Ask the SDK host to expand or restore the app's viewport surface. */\n\tsetHostViewportExpanded(expanded: boolean): void;\n\t/** Retry after a response outage banner. */\n\tretryAfterOutage(): void;\n\t/**\n\t * Report a fatal app error to core (renders the fallback UI and reports\n\t * telemetry). Callable from anywhere with a client reference — including\n\t * a class error boundary outside any React provider.\n\t */\n\treportAppError(error: unknown, componentStack?: string): void;\n}\n\nfunction mintUuid(): string {\n\tconst cryptoObj = typeof crypto === \"undefined\" ? undefined : crypto;\n\tif (typeof cryptoObj?.randomUUID === \"function\") {\n\t\treturn cryptoObj.randomUUID();\n\t}\n\tconst bytes = new Uint8Array(16);\n\tif (typeof cryptoObj?.getRandomValues === \"function\") {\n\t\tcryptoObj.getRandomValues(bytes);\n\t} else {\n\t\tfor (let index = 0; index < bytes.length; index += 1) {\n\t\t\tbytes[index] = Math.floor(Math.random() * 256);\n\t\t}\n\t}\n\t// RFC 4122 version/variant bits so core's uuid-shape validation accepts it.\n\tbytes[6] = ((bytes[6] as number) & 0x0f) | 0x40;\n\tbytes[8] = ((bytes[8] as number) & 0x3f) | 0x80;\n\tconst hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\"));\n\treturn `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex\n\t\t.slice(6, 8)\n\t\t.join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10).join(\"\")}`;\n}\n\ntype Outcome<T> = { value: T } | { error: Error };\n\n/**\n * The disposal hooks a settle promise registers with, so `dispose()` can\n * reject every in-flight `settled()` with a typed\n * {@link BridgeClientDestroyedError} — the layer's bound of last resort when\n * no `timeoutMs` was passed.\n */\ninterface SettleLifecycle {\n\tisDisposed(): boolean;\n\tregister(abort: (error: Error) => void): void;\n\tunregister(abort: (error: Error) => void): void;\n}\n\n/**\n * Settle a promise from state transitions: `inspect` runs once immediately\n * and then on every state update until it returns an outcome. `timeoutMs`\n * bounds the wait; without it an operation whose answer never arrives keeps\n * its subscription until it settles or the client is destroyed, which\n * rejects it with a {@link BridgeClientDestroyedError}.\n */\nfunction settleFromState<T>(\n\thost: BridgeOperationsHost,\n\tlifecycle: SettleLifecycle,\n\tinspect: (state: AppDisplayState | null) => Outcome<T> | undefined,\n\topts?: SettleOptions,\n): Promise<T> {\n\treturn new Promise<T>((resolve, reject) => {\n\t\tif (lifecycle.isDisposed()) {\n\t\t\treject(new BridgeClientDestroyedError());\n\t\t\treturn;\n\t\t}\n\t\tlet done = false;\n\t\tlet unsubscribe: (() => void) | null = null;\n\t\tlet timer: ReturnType<typeof setTimeout> | null = null;\n\t\tconst abort = (error: Error): void => {\n\t\t\tfinish({ error });\n\t\t};\n\t\tconst finish = (outcome: Outcome<T>): void => {\n\t\t\tif (done) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdone = true;\n\t\t\tlifecycle.unregister(abort);\n\t\t\tunsubscribe?.();\n\t\t\tif (timer !== null) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t}\n\t\t\tif (\"value\" in outcome) {\n\t\t\t\tresolve(outcome.value);\n\t\t\t} else {\n\t\t\t\treject(outcome.error);\n\t\t\t}\n\t\t};\n\t\tconst run = (): void => {\n\t\t\tconst outcome = inspect(host.getState());\n\t\t\tif (outcome) {\n\t\t\t\tfinish(outcome);\n\t\t\t}\n\t\t};\n\t\trun();\n\t\tif (done) {\n\t\t\treturn;\n\t\t}\n\t\tlifecycle.register(abort);\n\t\tunsubscribe = host.subscribe(run);\n\t\tif (opts?.timeoutMs !== undefined) {\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tfinish({\n\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t`Bridge operation timed out after ${opts.timeoutMs}ms.`,\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t}, opts.timeoutMs);\n\t\t}\n\t});\n}\n\ninterface StateObservation {\n\tsubscribeKey<K extends keyof AppDisplayState>(\n\t\tkey: K,\n\t\tcallback: (\n\t\t\tvalue: AppDisplayState[K] | undefined,\n\t\t\tprevious: AppDisplayState[K] | undefined,\n\t\t) => void,\n\t\topts?: ObserveOptions<AppDisplayState[K] | undefined>,\n\t): () => void;\n\tselect<T>(\n\t\tselector: (state: AppDisplayState | null) => T,\n\t\tcallback: (value: T, previous: T | undefined) => void,\n\t\topts?: ObserveOptions<T>,\n\t): () => void;\n}\n\n/**\n * Build the `subscribeKey`/`select` observation primitives over a host.\n * `Object.is` is a sound default equality for `subscribeKey`: state crosses\n * the frame boundary by structured clone, so the real client restores each\n * structurally unchanged key's previous reference before notifying (the test\n * mock shares references in memory to begin with). For `select`, whose\n * selector typically MINTS a new object per call, pass a custom `equals`\n * (or select a primitive/signature string) when projecting objects.\n */\nexport function createStateObservation(\n\thost: BridgeOperationsHost,\n): StateObservation {\n\tfunction select<T>(\n\t\tselector: (state: AppDisplayState | null) => T,\n\t\tcallback: (value: T, previous: T | undefined) => void,\n\t\topts?: ObserveOptions<T>,\n\t): () => void {\n\t\tconst equals = opts?.equals ?? Object.is;\n\t\tlet previous = selector(host.getState());\n\t\tif (opts?.emitInitial === true) {\n\t\t\tcallback(previous, undefined);\n\t\t}\n\t\treturn host.subscribe(() => {\n\t\t\tconst next = selector(host.getState());\n\t\t\tif (equals(next, previous)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst before = previous;\n\t\t\tprevious = next;\n\t\t\tcallback(next, before);\n\t\t});\n\t}\n\n\tfunction subscribeKey<K extends keyof AppDisplayState>(\n\t\tkey: K,\n\t\tcallback: (\n\t\t\tvalue: AppDisplayState[K] | undefined,\n\t\t\tprevious: AppDisplayState[K] | undefined,\n\t\t) => void,\n\t\topts?: ObserveOptions<AppDisplayState[K] | undefined>,\n\t): () => void {\n\t\treturn select((state) => state?.[key], callback, opts);\n\t}\n\n\treturn { subscribeKey, select };\n}\n\nexport interface BridgeOperationsBundle {\n\toperations: BridgeOperations;\n\t/** Release the layer's own subscriptions and timers (client teardown). */\n\tdispose(): void;\n}\n\nexport function createBridgeOperations(\n\trawHost: BridgeOperationsHost,\n): BridgeOperationsBundle {\n\tlet disposed = false;\n\n\t// Disposal must actually stop the layer: after `dispose()` no helper may\n\t// send another event or take a new subscription (the client's transport\n\t// is gone). Guarding the host once covers every helper below.\n\tconst host: BridgeOperationsHost = {\n\t\tgetState: () => rawHost.getState(),\n\t\tsubscribe: (callback) =>\n\t\t\tdisposed\n\t\t\t\t? () => {\n\t\t\t\t\t\t// nothing was subscribed\n\t\t\t\t\t}\n\t\t\t\t: rawHost.subscribe(callback),\n\t\tsendEvent: (event, ...args) => {\n\t\t\tif (!disposed) {\n\t\t\t\trawHost.sendEvent(event, ...args);\n\t\t\t}\n\t\t},\n\t};\n\n\tconst liveSettleAborts = new Set<(error: Error) => void>();\n\tconst lifecycle: SettleLifecycle = {\n\t\tisDisposed: () => disposed,\n\t\tregister: (abort) => {\n\t\t\tliveSettleAborts.add(abort);\n\t\t},\n\t\tunregister: (abort) => {\n\t\t\tliveSettleAborts.delete(abort);\n\t\t},\n\t};\n\n\t// markRead accumulator (mirrors the reference app's useReadTracking).\n\tlet readMax = \"\";\n\tlet readReported = \"\";\n\tlet readTimer: ReturnType<typeof setTimeout> | null = null;\n\tlet readConversationId: string | null = null;\n\tlet readResetUnsubscribe: (() => void) | null = null;\n\n\tfunction resetReadAccumulator(): void {\n\t\treadMax = \"\";\n\t\treadReported = \"\";\n\t\tif (readTimer !== null) {\n\t\t\tclearTimeout(readTimer);\n\t\t\treadTimer = null;\n\t\t}\n\t}\n\n\t// Cursor spaces are per conversation: when the pin changes, drop the\n\t// accumulator AND any pending debounced report so a cursor collected in\n\t// conversation A can never be committed against B.\n\tfunction ensureReadResetSubscription(): void {\n\t\tif (readResetUnsubscribe) {\n\t\t\treturn;\n\t\t}\n\t\tlet previousPin = host.getState()?.[\"chat.conversationId\"] ?? null;\n\t\treadResetUnsubscribe = host.subscribe(() => {\n\t\t\tconst pin = host.getState()?.[\"chat.conversationId\"] ?? null;\n\t\t\tif (pin !== previousPin) {\n\t\t\t\tpreviousPin = pin;\n\t\t\t\tresetReadAccumulator();\n\t\t\t\treadConversationId = pin;\n\t\t\t}\n\t\t});\n\t}\n\n\tfunction scheduleReadReport(): void {\n\t\tif (readTimer !== null) {\n\t\t\treturn;\n\t\t}\n\t\treadTimer = setTimeout(() => {\n\t\t\treadTimer = null;\n\t\t\tif (readMax > readReported) {\n\t\t\t\treadReported = readMax;\n\t\t\t\thost.sendEvent(\"chat.messages.read\", {\n\t\t\t\t\tcursor: readMax,\n\t\t\t\t\t...(readConversationId ? { conversationId: readConversationId } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t}, READ_REPORT_DEBOUNCE_MS);\n\t}\n\n\tlet newConversationBlockedUntil = 0;\n\n\tconst operations: BridgeOperations = {\n\t\tsendMessage(body, opts) {\n\t\t\tconst trimmed = typeof body === \"string\" ? body.trim() : \"\";\n\t\t\tconst secret = opts?.secret === true;\n\t\t\tconst tempMessageUuid = mintUuid();\n\t\t\tlet errorBaseline: string | null = null;\n\t\t\tlet errorSeqBaseline: number | null = null;\n\t\t\tif (trimmed) {\n\t\t\t\tconst preSend = host.getState();\n\t\t\t\terrorBaseline = preSend?.[\"chat.error\"] ?? null;\n\t\t\t\terrorSeqBaseline = preSend?.[\"chat.error.seq\"] ?? null;\n\t\t\t\thost.sendEvent(\"chat.message.send\", {\n\t\t\t\t\tbody: trimmed,\n\t\t\t\t\tmessageType: secret ? \"secret\" : \"text\",\n\t\t\t\t\ttempMessageUuid,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttempMessageUuid,\n\t\t\t\tsettled(settleOpts) {\n\t\t\t\t\tif (!trimmed) {\n\t\t\t\t\t\treturn Promise.reject(\n\t\t\t\t\t\t\tnew Error(\"sendMessage requires a non-empty body.\"),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (secret) {\n\t\t\t\t\t\t// No optimistic row exists for a secret send by design, so\n\t\t\t\t\t\t// there is nothing to resolve with. Time-bound or skip; the\n\t\t\t\t\t\t// wait is still bounded by client destroy.\n\t\t\t\t\t\treturn settleFromState<Message>(\n\t\t\t\t\t\t\thost,\n\t\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn settleFromState<Message>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst messages = state?.[\"chat.messages\"] ?? [];\n\t\t\t\t\t\t\tconst match = messages.find(\n\t\t\t\t\t\t\t\t(message) => message.clientKey === tempMessageUuid,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\treturn { value: match };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst error = state?.[\"chat.error\"] ?? null;\n\t\t\t\t\t\t\t// A core that stamps `chat.error.clientKey` (always present,\n\t\t\t\t\t\t\t// null when unattributed) attributes every pre-row send\n\t\t\t\t\t\t\t// failure to the send's own key, so the correlation is exact:\n\t\t\t\t\t\t\t// only THIS send's error rejects, and an unrelated failure\n\t\t\t\t\t\t\t// landing between the send and its optimistic row (the seq\n\t\t\t\t\t\t\t// fallback's false-rejection window) is ignored.\n\t\t\t\t\t\t\tif (state !== null && \"chat.error.clientKey\" in state) {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\terror !== null &&\n\t\t\t\t\t\t\t\t\tstate[\"chat.error.clientKey\"] === tempMessageUuid\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst errorSeq = state?.[\"chat.error.seq\"];\n\t\t\t\t\t\t\tif (typeof errorSeq === \"number\") {\n\t\t\t\t\t\t\t\tif (errorSeqBaseline === null) {\n\t\t\t\t\t\t\t\t\t// The send raced state hydration (no state yet when the\n\t\t\t\t\t\t\t\t\t// baseline was captured): latch the first observed seq\n\t\t\t\t\t\t\t\t\t// and treat the current error as pre-existing.\n\t\t\t\t\t\t\t\t\terrorSeqBaseline = errorSeq;\n\t\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// The seq advances on EVERY error set — including a repeat\n\t\t\t\t\t\t\t\t// of the identical text, which the fallback below cannot\n\t\t\t\t\t\t\t\t// see. `!==` rather than `>`, so a counter restarted by a\n\t\t\t\t\t\t\t\t// full core reset still rejects.\n\t\t\t\t\t\t\t\tif (error !== null && errorSeq !== errorSeqBaseline) {\n\t\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Core document predates chat.error.seq: fall back to text\n\t\t\t\t\t\t\t// transitions (a repeated identical error is invisible here).\n\t\t\t\t\t\t\tif (error === null) {\n\t\t\t\t\t\t\t\terrorBaseline = null;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (error !== errorBaseline) {\n\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tretryMessage(messageId) {\n\t\t\thost.sendEvent(\"chat.message.retry\", {\n\t\t\t\tmessage: { id: messageId, type: \"text\" },\n\t\t\t});\n\t\t},\n\n\t\tcanRetryMessage(message) {\n\t\t\tconst state = host.getState();\n\t\t\treturn (\n\t\t\t\tmessage.type === \"text\" &&\n\t\t\t\tmessage.isRetryable === true &&\n\t\t\t\tstate?.[\"zendesk.sdk.disconnected\"] !== true &&\n\t\t\t\t(message.conversationId == null ||\n\t\t\t\t\tmessage.conversationId === (state?.[\"chat.conversationId\"] ?? null))\n\t\t\t);\n\t\t},\n\n\t\tselectQuickReply(reply, messageId) {\n\t\t\thost.sendEvent(\"chat.quickReply.select\", {\n\t\t\t\tlabel: reply.label,\n\t\t\t\ttarget: reply.target,\n\t\t\t\tbuttonType: reply.buttonType,\n\t\t\t\t...(reply.value !== undefined ? { value: reply.value } : {}),\n\t\t\t\tmessageId,\n\t\t\t});\n\t\t},\n\n\t\tselectOption(option, messageId) {\n\t\t\thost.sendEvent(\"chat.option.select\", {\n\t\t\t\toptionId: option.id,\n\t\t\t\toptionLabel: option.label || option.id,\n\t\t\t\tmessageId,\n\t\t\t});\n\t\t},\n\n\t\tsubmitListSelection(selectedIds) {\n\t\t\tconst listSelection =\n\t\t\t\thost.getState()?.[\"conversation.stateData.listSelection\"];\n\t\t\tif (!listSelection) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"chat.selectableList.submit\", {\n\t\t\t\tselectedIds,\n\t\t\t\tdataId: listSelection.dataId,\n\t\t\t});\n\t\t},\n\n\t\tcancelListSelection(label) {\n\t\t\tconst listSelection =\n\t\t\t\thost.getState()?.[\"conversation.stateData.listSelection\"];\n\t\t\t// Core drops a cancel without a response id, so don't send one.\n\t\t\tif (!listSelection?.cancelCaptureResponseId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"listSelection.cancel\", {\n\t\t\t\tcancelResponseId: listSelection.cancelCaptureResponseId,\n\t\t\t\tcancelLabel: label ?? listSelection.cancelCaptureLabel,\n\t\t\t\tdataId: listSelection.dataId,\n\t\t\t});\n\t\t},\n\n\t\tsubmitCapture(value) {\n\t\t\tconst capture = host.getState()?.[\"conversation.stateData.capture\"];\n\t\t\tif (!capture) {\n\t\t\t\treturn {\n\t\t\t\t\tsettled: () =>\n\t\t\t\t\t\tPromise.reject(new Error(\"No capture field is active.\")),\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst dataId = capture.dataId;\n\t\t\tconst submitId = mintUuid();\n\t\t\thost.sendEvent(\"capture.submit\", { value, dataId, submitId });\n\t\t\t// The verdict rides the capture object itself: `isSubmitting` is\n\t\t\t// core-owned, flips true while the submit is in flight, and the\n\t\t\t// falling edge carries `validationErrorMessage`; an ACCEPTED value\n\t\t\t// removes the whole object (or replaces it with the next block).\n\t\t\t// Every failure or drop WITHOUT a server validation verdict — a\n\t\t\t// pre-flight drop that raises and settles `isSubmitting` inside one\n\t\t\t// broadcast batch, a transport failure, a duplicate submit dropped\n\t\t\t// while another is in flight — lands on `lastSubmitFailure` keyed\n\t\t\t// with this call's own `submitId`, so the correlation is exact and\n\t\t\t// the global `chat.error` is never consulted.\n\t\t\tlet sawSubmitting = false;\n\t\t\treturn {\n\t\t\t\tsettled: (settleOpts) =>\n\t\t\t\t\tsettleFromState<OperationResult>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst current = state?.[\"conversation.stateData.capture\"] ?? null;\n\t\t\t\t\t\t\tif (!current || current.dataId !== dataId) {\n\t\t\t\t\t\t\t\treturn { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Keyed failure BEFORE the in-flight latch: a duplicate\n\t\t\t\t\t\t\t// submit's drop is stamped while the OTHER submit still holds\n\t\t\t\t\t\t\t// `isSubmitting`, and latching that flag here would make this\n\t\t\t\t\t\t\t// handle inherit the other submit's verdict.\n\t\t\t\t\t\t\tconst failure = current.lastSubmitFailure;\n\t\t\t\t\t\t\tif (failure && failure.submitId === submitId) {\n\t\t\t\t\t\t\t\treturn { value: { ok: false, message: failure.message } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (current.isSubmitting === true) {\n\t\t\t\t\t\t\t\tsawSubmitting = true;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (sawSubmitting) {\n\t\t\t\t\t\t\t\treturn current.validationErrorMessage\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\t\t\t\t\t\tok: false,\n\t\t\t\t\t\t\t\t\t\t\t\tmessage: current.validationErrorMessage,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t),\n\t\t\t};\n\t\t},\n\n\t\tcancelCapture(label) {\n\t\t\tconst capture = host.getState()?.[\"conversation.stateData.capture\"];\n\t\t\tif (!capture?.cancelCaptureResponseId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"capture.cancel\", {\n\t\t\t\tcancelResponseId: capture.cancelCaptureResponseId,\n\t\t\t\tcancelLabel: label ?? capture.cancelCaptureLabel,\n\t\t\t\tdataId: capture.dataId,\n\t\t\t});\n\t\t},\n\n\t\taddReaction(messageId, reaction) {\n\t\t\thost.sendEvent(\"chat.message.reaction.add\", { messageId, reaction });\n\t\t},\n\n\t\treportLinkClick(messageId, opts) {\n\t\t\thost.sendEvent(\"chat.link.click\", {\n\t\t\t\tmessageId,\n\t\t\t\t...(opts?.navigateInHost !== undefined\n\t\t\t\t\t? { navigateInHost: opts.navigateInHost }\n\t\t\t\t\t: {}),\n\t\t\t});\n\t\t},\n\n\t\tmarkRead(cursor, opts) {\n\t\t\tif (typeof cursor !== \"string\" || cursor === \"\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tensureReadResetSubscription();\n\t\t\tconst conversationId =\n\t\t\t\topts?.conversationId ??\n\t\t\t\thost.getState()?.[\"chat.conversationId\"] ??\n\t\t\t\tnull;\n\t\t\tif (conversationId !== readConversationId) {\n\t\t\t\tresetReadAccumulator();\n\t\t\t\treadConversationId = conversationId;\n\t\t\t}\n\t\t\tif (cursor > readMax) {\n\t\t\t\treadMax = cursor;\n\t\t\t\tscheduleReadReport();\n\t\t\t}\n\t\t},\n\n\t\tloadOlderMessages() {\n\t\t\tconst state = host.getState();\n\t\t\tif (\n\t\t\t\tstate?.[\"chat.canLoadMore\"] !== true ||\n\t\t\t\tstate[\"chat.loadingPreviousMessages\"] === true\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"chat.logs.loadMore\");\n\t\t},\n\n\t\tonHistoryLoaded(callback) {\n\t\t\tlet previous = host.getState()?.[\"chat.recentMessagesLoadSeq\"] ?? 0;\n\t\t\treturn host.subscribe(() => {\n\t\t\t\tconst seq = host.getState()?.[\"chat.recentMessagesLoadSeq\"] ?? 0;\n\t\t\t\tif (seq > previous) {\n\t\t\t\t\tprevious = seq;\n\t\t\t\t\tcallback(seq);\n\t\t\t\t} else if (seq < previous) {\n\t\t\t\t\t// Returned to 0 on a chat clear; re-arm without firing.\n\t\t\t\t\tprevious = seq;\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tsubmitCsat(data, opts) {\n\t\t\tconst { surveyType, partial, conversationId } = opts;\n\t\t\tconst baselineSeq = host.getState()?.[\"csat.submitSuccess.seq\"] ?? 0;\n\t\t\tlet errorBaseline = host.getState()?.[\"csat.submitError\"] ?? null;\n\t\t\tlet errorSeqBaseline = host.getState()?.[\"csat.submitError.seq\"] ?? null;\n\t\t\t// `null` means \"omitted\" on the wire AND in correlation: core's\n\t\t\t// resolver treats a falsy id as absent and reports the SESSION's id\n\t\t\t// back, so a null-scoped predicate could never match it and the\n\t\t\t// handle would hang on a fully successful submit.\n\t\t\thost.sendEvent(\"csat.submit\", {\n\t\t\t\tdata,\n\t\t\t\tsurveyType,\n\t\t\t\t...(partial !== undefined ? { partial } : {}),\n\t\t\t\t...(conversationId != null ? { conversationId } : {}),\n\t\t\t});\n\t\t\t// When the caller omitted conversationId (End Chat / proactive),\n\t\t\t// core resolves the conversation itself, so match on the type +\n\t\t\t// seq alone; a caller-scoped survey matches its exact conversation.\n\t\t\tconst matchesConversation = (\n\t\t\t\tcandidate: string | null | undefined,\n\t\t\t): boolean =>\n\t\t\t\tconversationId == null || (candidate ?? null) === conversationId;\n\t\t\treturn {\n\t\t\t\tsettled: (settleOpts) =>\n\t\t\t\t\tsettleFromState<OperationResult>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst seq = state?.[\"csat.submitSuccess.seq\"] ?? 0;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tseq > baselineSeq &&\n\t\t\t\t\t\t\t\tstate?.[\"csat.submitSuccess.surveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\tmatchesConversation(\n\t\t\t\t\t\t\t\t\tstate?.[\"csat.submitSuccess.conversationId\"],\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst error = state?.[\"csat.submitError\"] ?? null;\n\t\t\t\t\t\t\tconst errorSeq = state?.[\"csat.submitError.seq\"];\n\t\t\t\t\t\t\tif (typeof errorSeq === \"number\") {\n\t\t\t\t\t\t\t\t// The seq advances on EVERY error set — including a repeat\n\t\t\t\t\t\t\t\t// of the identical text (the same submit dropped twice at a\n\t\t\t\t\t\t\t\t// core gate), which the text transition below cannot see.\n\t\t\t\t\t\t\t\tif (errorSeqBaseline === null) {\n\t\t\t\t\t\t\t\t\t// The submit raced state hydration: latch the first\n\t\t\t\t\t\t\t\t\t// observed seq and treat the current error as\n\t\t\t\t\t\t\t\t\t// pre-existing.\n\t\t\t\t\t\t\t\t\terrorSeqBaseline = errorSeq;\n\t\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\terror !== null &&\n\t\t\t\t\t\t\t\t\terrorSeq !== errorSeqBaseline &&\n\t\t\t\t\t\t\t\t\tstate?.[\"csat.submitErrorSurveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\t\tmatchesConversation(state?.[\"csat.submitErrorConversationId\"])\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn { value: { ok: false, message: error } };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Core document predates csat.submitError.seq: fall back to\n\t\t\t\t\t\t\t// text transitions.\n\t\t\t\t\t\t\tif (error === null) {\n\t\t\t\t\t\t\t\t// Core clears the error at the start of every\n\t\t\t\t\t\t\t\t// non-partial submit; from here any error is ours.\n\t\t\t\t\t\t\t\terrorBaseline = null;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\terror !== errorBaseline &&\n\t\t\t\t\t\t\t\tstate?.[\"csat.submitErrorSurveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\tmatchesConversation(state?.[\"csat.submitErrorConversationId\"])\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn { value: { ok: false, message: error } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t),\n\t\t\t};\n\t\t},\n\n\t\ttrackCsatShown(surveyType, conversationId) {\n\t\t\thost.sendEvent(\"csat.shown\", {\n\t\t\t\tsurveyType,\n\t\t\t\t...(conversationId != null ? { conversationId } : {}),\n\t\t\t});\n\t\t},\n\n\t\tcheckEndChatEligibility(opts) {\n\t\t\tconst baselineSeq = host.getState()?.[\"csat.endChatEligibility.seq\"] ?? 0;\n\t\t\thost.sendEvent(\"csat.checkEndChatEligibility\");\n\t\t\treturn settleFromState<EndChatEligibilityResult>(\n\t\t\t\thost,\n\t\t\t\tlifecycle,\n\t\t\t\t(state) => {\n\t\t\t\t\tconst seq = state?.[\"csat.endChatEligibility.seq\"] ?? 0;\n\t\t\t\t\tif (seq <= baselineSeq) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\teligible: state?.[\"csat.endChatEligible\"] ?? null,\n\t\t\t\t\t\t\tsurveyTarget: state?.[\"csat.endChatSurveyTarget\"] ?? null,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\n\t\tskipCsatAndEndChat() {\n\t\t\thost.sendEvent(\"endChat.skipCsat\");\n\t\t},\n\n\t\tendLiveChat() {\n\t\t\thost.sendEvent(\"endChat.endLiveChat\");\n\t\t},\n\n\t\tstartNewConversation(opts) {\n\t\t\tif (host.getState()?.[\"app.conversationStartPending\"] === true) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst now = Date.now();\n\t\t\tif (now < newConversationBlockedUntil) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tnewConversationBlockedUntil =\n\t\t\t\tnow + (opts?.cooldownMs ?? START_NEW_CONVERSATION_COOLDOWN_MS);\n\t\t\thost.sendEvent(\"app.startNewConversation\");\n\t\t\treturn true;\n\t\t},\n\n\t\tresetApp() {\n\t\t\thost.sendEvent(\"app.reset\");\n\t\t},\n\n\t\tnotifyComposerChanged(opts) {\n\t\t\t// Even a content-free typing edge is a signal that a secret is\n\t\t\t// being typed, so it stays suppressed at the source.\n\t\t\tif (\n\t\t\t\topts?.secret === true ||\n\t\t\t\thost.getState()?.[\"ui.secretMessage.isOpen\"] === true\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"ui.composer.textChanged\");\n\t\t},\n\n\t\ttoggleSecretMessage(open) {\n\t\t\thost.sendEvent(\"ui.secretMessage.toggle\", { open });\n\t\t},\n\n\t\tstartFileUpload(file) {\n\t\t\tconst uploadId = mintUuid();\n\t\t\thost.sendEvent(\"file.upload.start\", { file, uploadId });\n\t\t\treturn {\n\t\t\t\tuploadId,\n\t\t\t\tretry() {\n\t\t\t\t\thost.sendEvent(\"file.upload.retry\", { file, uploadId });\n\t\t\t\t},\n\t\t\t\tdismissError() {\n\t\t\t\t\thost.sendEvent(\"file.upload.errorDismiss\");\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tsetLanguage(language) {\n\t\t\thost.sendEvent(\"settings.language.change\", { language });\n\t\t},\n\n\t\temailTranscript(email, opts) {\n\t\t\t// Core drops an empty email silently — no status transition ever —\n\t\t\t// so validate here and reject immediately instead of hanging.\n\t\t\tconst trimmed = typeof email === \"string\" ? email.trim() : \"\";\n\t\t\tif (!trimmed) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(\"emailTranscript requires a non-empty email.\"),\n\t\t\t\t);\n\t\t\t}\n\t\t\thost.sendEvent(\"settings.transcript.email.send\", { email: trimmed });\n\t\t\treturn transcriptSettled(\"transcript.email.status\", opts, trimmed);\n\t\t},\n\n\t\tdownloadTranscript(opts) {\n\t\t\thost.sendEvent(\"settings.transcript.download.request\");\n\t\t\treturn transcriptSettled(\"transcript.download.status\", opts);\n\t\t},\n\n\t\tsetAlertSound(enabled) {\n\t\t\thost.sendEvent(\"settings.alertSound.toggle\", { enabled });\n\t\t},\n\n\t\trequestNotificationPermission() {\n\t\t\thost.sendEvent(\"settings.notifications.requestPermission\");\n\t\t},\n\n\t\tsetTheme(theme) {\n\t\t\thost.sendEvent(\"appearance.setTheme\", { theme });\n\t\t},\n\n\t\tsetTextSize(size) {\n\t\t\thost.sendEvent(\"appearance.setTextSize\", { textSize: size });\n\t\t},\n\n\t\tclose() {\n\t\t\thost.sendEvent(\"ui.closeButton.click\");\n\t\t},\n\n\t\tminimize() {\n\t\t\thost.sendEvent(\"ui.minimizeButton.click\");\n\t\t},\n\n\t\tdismissToast() {\n\t\t\thost.sendEvent(\"ui.toast.dismiss\");\n\t\t},\n\n\t\tdismissError() {\n\t\t\thost.sendEvent(\"chat.error.dismiss\");\n\t\t},\n\n\t\tsetHostViewportExpanded(expanded) {\n\t\t\thost.sendEvent(\"ui.hostViewport.set\", { expanded });\n\t\t},\n\n\t\tretryAfterOutage() {\n\t\t\thost.sendEvent(\"outage.retry\");\n\t\t},\n\n\t\treportAppError(error, componentStack) {\n\t\t\tconst err = error instanceof Error ? error : null;\n\t\t\thost.sendEvent(\"app.error.report\", {\n\t\t\t\tmessage: err?.message ?? String(error),\n\t\t\t\tstack: err?.stack ?? \"\",\n\t\t\t\tcomponentStack: componentStack ?? \"\",\n\t\t\t});\n\t\t},\n\t};\n\n\tfunction transcriptSettled(\n\t\tkey: \"transcript.email.status\" | \"transcript.download.status\",\n\t\topts?: SettleOptions,\n\t\t// Email only: reject THIS call when core rejects a conflicting request\n\t\t// for exactly this address on `transcript.email.conflict.*` — the\n\t\t// shared status channel never carries the conflict, so the in-flight\n\t\t// caller settles on its own verdict.\n\t\tconflictEmail?: string,\n\t): Promise<void> {\n\t\tconst seqKey = `${key}.seq` as\n\t\t\t| \"transcript.email.status.seq\"\n\t\t\t| \"transcript.download.status.seq\";\n\t\tlet previous = host.getState()?.[key] ?? \"idle\";\n\t\tlet seqBaseline = host.getState()?.[seqKey] ?? null;\n\t\tlet conflictSeqBaseline =\n\t\t\thost.getState()?.[\"transcript.email.conflict.seq\"] ?? null;\n\t\treturn settleFromState<void>(\n\t\t\thost,\n\t\t\tlifecycle,\n\t\t\t(state) => {\n\t\t\t\tif (conflictEmail !== undefined) {\n\t\t\t\t\tconst conflictSeq = state?.[\"transcript.email.conflict.seq\"];\n\t\t\t\t\tif (typeof conflictSeq === \"number\") {\n\t\t\t\t\t\tif (conflictSeqBaseline === null) {\n\t\t\t\t\t\t\t// The request raced state hydration: latch the first\n\t\t\t\t\t\t\t// observed seq, mirroring the status-seq baseline below.\n\t\t\t\t\t\t\t// Residual: if that first snapshot already carries THIS\n\t\t\t\t\t\t\t// call's own conflict, the reject branch never fires and\n\t\t\t\t\t\t\t// the handle falls through to the shared-status\n\t\t\t\t\t\t\t// correlation — the same bounded degradation as the\n\t\t\t\t\t\t\t// documented old-core (no conflict keys) skew. Not\n\t\t\t\t\t\t\t// closable from here: with no pre-call baseline, a\n\t\t\t\t\t\t\t// matching-address conflict in the first snapshot is\n\t\t\t\t\t\t\t// indistinguishable from a pre-existing one, and\n\t\t\t\t\t\t\t// treating it as ours would false-reject.\n\t\t\t\t\t\t\tconflictSeqBaseline = conflictSeq;\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tconflictSeq !== conflictSeqBaseline &&\n\t\t\t\t\t\t\tstate?.[\"transcript.email.conflict.email\"] === conflictEmail\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\t\t\"emailTranscript was rejected: a request for a different \" +\n\t\t\t\t\t\t\t\t\t\t\"address is already in flight. The in-flight email \" +\n\t\t\t\t\t\t\t\t\t\t\"still completes with its own outcome.\",\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst status = state?.[key] ?? \"idle\";\n\t\t\t\tconst seq = state?.[seqKey];\n\t\t\t\tif (typeof seq === \"number\") {\n\t\t\t\t\t// The seq advances on every status write, so a request core\n\t\t\t\t\t// drops straight to a terminal status (never `pending` in a\n\t\t\t\t\t// broadcast snapshot) and a repeat of the previous terminal\n\t\t\t\t\t// status both stay observable.\n\t\t\t\t\tif (seqBaseline === null) {\n\t\t\t\t\t\t// The request raced state hydration: latch the first observed\n\t\t\t\t\t\t// seq and treat the current status as pre-existing.\n\t\t\t\t\t\tseqBaseline = seq;\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tif (seq === seqBaseline) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tif (status === \"success\") {\n\t\t\t\t\t\treturn { value: undefined };\n\t\t\t\t\t}\n\t\t\t\t\tif (status === \"error\") {\n\t\t\t\t\t\treturn { error: new Error(`${key} reported an error.`) };\n\t\t\t\t\t}\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\t// Core document predates the seq key: fall back to the\n\t\t\t\t// pending -> terminal edge.\n\t\t\t\tconst was = previous;\n\t\t\t\tprevious = status;\n\t\t\t\tif (was !== \"pending\") {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tif (status === \"success\") {\n\t\t\t\t\treturn { value: undefined };\n\t\t\t\t}\n\t\t\t\tif (status === \"error\") {\n\t\t\t\t\treturn { error: new Error(`${key} reported an error.`) };\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n\n\treturn {\n\t\toperations,\n\t\tdispose() {\n\t\t\tif (disposed) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdisposed = true;\n\t\t\tresetReadAccumulator();\n\t\t\treadResetUnsubscribe?.();\n\t\t\treadResetUnsubscribe = null;\n\t\t\t// Reject every in-flight settled() promise: the transport is gone,\n\t\t\t// so no state transition can ever answer it. Snapshot first — each\n\t\t\t// abort unregisters itself from the live set.\n\t\t\tfor (const abort of [...liveSettleAborts]) {\n\t\t\t\tabort(new BridgeClientDestroyedError());\n\t\t\t}\n\t\t\tliveSettleAborts.clear();\n\t\t},\n\t};\n}\n","import type { BridgeClient } from \"../contract/bridge-client\";\nimport {\n\tcreateBridgeOperations,\n\tcreateStateObservation,\n} from \"../contract/operations\";\nimport type { AppDisplayState, AppEvents } from \"../contract/types\";\n\n/** One recorded `sendEvent` call. */\nexport interface SentBridgeEvent<K extends keyof AppEvents = keyof AppEvents> {\n\tevent: K;\n\tpayload: AppEvents[K] | undefined;\n}\n\n/**\n * A scriptable, in-memory {@link BridgeClient} for unit tests.\n *\n * No postMessage, no origin checks, no network: tests drive state through\n * `setState`/`updateState` and assert on the events the component under test\n * sent through `sentEvents`. The `operations`, `subscribeKey`, and `select`\n * surfaces are the REAL implementations running over this in-memory state:\n * every operation records its events in `sentEvents`, and a correlated\n * handle (`sendMessage().settled()`, `submitCsat().settled()`, ...) resolves\n * when the test scripts the answering state transition via `updateState`.\n */\nexport interface MockBridgeClient extends BridgeClient {\n\t/** Replace the state snapshot and notify subscribers. */\n\tsetState(state: AppDisplayState | null): void;\n\t/**\n\t * Shallow-merge a partial snapshot over the current state (or over an empty\n\t * object when no state is set) and notify subscribers. Seeds\n\t * `\"chat.error.clientKey\": null` when the key is absent — production core\n\t * always publishes it (null when no keyed send owns the error), and\n\t * `operations` send handles take the keyed correlation path only when it is\n\t * present. Script a `chat.error` fixture with the send's `tempMessageUuid`\n\t * as `chat.error.clientKey` to reject that handle; use `setState` to script\n\t * an older core document without the key.\n\t */\n\tupdateState(partial: Partial<AppDisplayState>): void;\n\t/**\n\t * Every event sent through the client, in order. Each read returns a fresh\n\t * copy, so a captured array survives `clearSentEvents()`.\n\t */\n\treadonly sentEvents: readonly SentBridgeEvent[];\n\t/** Forget recorded events without touching state or subscribers. */\n\tclearSentEvents(): void;\n\t/** Number of active subscribers. */\n\treadonly subscriberCount: number;\n\t/** True once `destroy()` has been called. */\n\treadonly destroyed: boolean;\n}\n\n/**\n * Create a {@link MockBridgeClient}, optionally seeded with an initial state\n * snapshot. Mirrors the real client's teardown semantics: after `destroy()`,\n * state is `null`, subscribers are cleared, `sendEvent` is a no-op, and every\n * in-flight `operations` `settled()` promise rejects with a\n * `BridgeClientDestroyedError`. Notification also mirrors the real client:\n * a throwing subscriber is caught and logged, never aborting the pass.\n *\n * The derivation helpers (`filterDisplayable`, `groupMessages`, `messageKey`,\n * `selectUnread`, ...) are runtime-only: this package types them on\n * `MessagingBridgeModule` but ships no implementation, so there is no mock\n * module to destructure them from. In unit tests, inject them into the code\n * under test (plain function parameters or props); to exercise the real\n * implementations, load the module with `loadMessagingBridge()` in an\n * integration test. Do not re-implement them — a copy drifts from the\n * runtime's edge cases.\n */\nexport function createMockBridgeClient(\n\tinitialState: AppDisplayState | null = null,\n): MockBridgeClient {\n\tlet state = initialState;\n\tlet destroyed = false;\n\tconst subscribers = new Set<() => void>();\n\tconst sentEvents: SentBridgeEvent[] = [];\n\n\tfunction notify(): void {\n\t\t// Mirrors the real client's notify(): one throwing subscriber must not\n\t\t// abort the pass (a settled() promise resolved from a later subscriber\n\t\t// would otherwise hang) or skip subscribers that unsubscribe mid-pass.\n\t\tfor (const callback of [...subscribers]) {\n\t\t\ttry {\n\t\t\t\tcallback();\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[AdaMessaging] Bridge subscriber threw:\", err);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction getState(): AppDisplayState | null {\n\t\treturn state;\n\t}\n\n\tfunction subscribe(callback: () => void): () => void {\n\t\tsubscribers.add(callback);\n\t\treturn () => {\n\t\t\tsubscribers.delete(callback);\n\t\t};\n\t}\n\n\tfunction sendEvent<K extends keyof AppEvents>(\n\t\tevent: K,\n\t\t...args: AppEvents[K] extends undefined ? [] : [payload: AppEvents[K]]\n\t): void {\n\t\tif (destroyed) {\n\t\t\treturn;\n\t\t}\n\t\tsentEvents.push({ event, payload: args[0] });\n\t}\n\n\tconst host = { getState, subscribe, sendEvent };\n\tconst observation = createStateObservation(host);\n\tconst operationsBundle = createBridgeOperations(host);\n\n\treturn {\n\t\tgetState,\n\t\tsubscribe,\n\t\tsendEvent,\n\t\tsubscribeKey: observation.subscribeKey,\n\t\tselect: observation.select,\n\t\toperations: operationsBundle.operations,\n\n\t\tdestroy(): void {\n\t\t\tdestroyed = true;\n\t\t\toperationsBundle.dispose();\n\t\t\tstate = null;\n\t\t\tsubscribers.clear();\n\t\t},\n\n\t\tsetState(next: AppDisplayState | null): void {\n\t\t\tstate = next;\n\t\t\tnotify();\n\t\t},\n\n\t\tupdateState(partial: Partial<AppDisplayState>): void {\n\t\t\t// Production core always publishes `chat.error.clientKey` (null when\n\t\t\t// unattributed), and `operations` feature-detects on its presence.\n\t\t\t// Seed it so a scripted failure exercises the keyed path production\n\t\t\t// takes; override it in `partial` (or use setState) to script an\n\t\t\t// older core document.\n\t\t\tstate = {\n\t\t\t\t\"chat.error.clientKey\": null,\n\t\t\t\t...(state ?? {}),\n\t\t\t\t...partial,\n\t\t\t} as AppDisplayState;\n\t\t\tnotify();\n\t\t},\n\n\t\tget sentEvents(): readonly SentBridgeEvent[] {\n\t\t\treturn [...sentEvents];\n\t\t},\n\n\t\tclearSentEvents(): void {\n\t\t\tsentEvents.length = 0;\n\t\t},\n\n\t\tget subscriberCount(): number {\n\t\t\treturn subscribers.size;\n\t\t},\n\n\t\tget destroyed(): boolean {\n\t\t\treturn destroyed;\n\t\t},\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../../../../shared/utils/src/browser-polyfills.ts","../../src/contract/operations.ts","../../src/npm/testing.ts"],"sourcesContent":["/**\n * Defence in depth for engines just below the declared browser floor.\n * The floor is defined in `messaging/vite.browser-targets.mjs`.\n *\n * Internal Ada code should prefer direct helpers, such as `hasOwn(...)` from\n * `guards.ts`. Every Ada bundle includes these fallbacks, including the host-page\n * SDK. These fallbacks also ship inside the customer-held npm tarballs of the SDK\n * and bridge. Each fallback defines a member only when it is missing. These\n * fallbacks do not extend browser support below the floor required by the shipped CSS.\n */\n\nconst objectHasOwnProperty = Object.prototype.hasOwnProperty;\n\nfunction defineValue(\n\ttarget: object,\n\tname: PropertyKey,\n\tvalue: (...args: unknown[]) => unknown,\n): void {\n\tObject.defineProperty(target, name, {\n\t\tvalue,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t});\n}\n\nif (!Object.hasOwn) {\n\tdefineValue(Object, \"hasOwn\", (object: unknown, key: unknown) => {\n\t\tif (object == null) {\n\t\t\tthrow new TypeError(\n\t\t\t\t\"ada browser-polyfills: Cannot convert undefined or null to object\",\n\t\t\t);\n\t\t}\n\t\treturn objectHasOwnProperty.call(Object(object), key as PropertyKey);\n\t});\n}\n\nfunction at<T>(\n\tthis: { length: number; [index: number]: T },\n\tindex: unknown,\n): T | undefined {\n\tconst length = Math.trunc(Number(this.length)) || 0;\n\tlet relativeIndex = Math.trunc(Number(index)) || 0;\n\tif (relativeIndex < 0) {\n\t\trelativeIndex += length;\n\t}\n\treturn relativeIndex < 0 || relativeIndex >= length\n\t\t? undefined\n\t\t: this[relativeIndex];\n}\n\nfunction stringAt(this: string, index: unknown): string | undefined {\n\tconst value = String(this);\n\tconst length = value.length;\n\tlet relativeIndex = Math.trunc(Number(index)) || 0;\n\tif (relativeIndex < 0) {\n\t\trelativeIndex += length;\n\t}\n\treturn relativeIndex < 0 || relativeIndex >= length\n\t\t? undefined\n\t\t: value.charAt(relativeIndex);\n}\n\nconst typedArrayConstructors = [\n\tglobalThis.Int8Array,\n\tglobalThis.Uint8Array,\n\tglobalThis.Uint8ClampedArray,\n\tglobalThis.Int16Array,\n\tglobalThis.Uint16Array,\n\tglobalThis.Int32Array,\n\tglobalThis.Uint32Array,\n\tglobalThis.Float32Array,\n\tglobalThis.Float64Array,\n\tglobalThis.BigInt64Array,\n\tglobalThis.BigUint64Array,\n].filter(Boolean);\n\nif (!Array.prototype.at) {\n\tdefineValue(Array.prototype, \"at\", at);\n}\n\nif (!String.prototype.at) {\n\tdefineValue(String.prototype, \"at\", stringAt);\n}\n\nfor (const TypedArrayConstructor of typedArrayConstructors) {\n\tconst prototype = TypedArrayConstructor.prototype;\n\tif (!prototype.at) {\n\t\tdefineValue(prototype, \"at\", at);\n\t}\n}\n","import type { CustomAppFrameStyles } from \"@shared/utils\";\nimport type {\n\tAppDisplayState,\n\tAppEvents,\n\tCsatSubmitData,\n\tMessage,\n\tOptionItem,\n\tQuickReply,\n} from \"./types\";\n\n/**\n * `client.operations` — typed helpers over the raw event/state contract,\n * mined from Ada's reference app so custom apps inherit its validated\n * behavior (debounces, guards, correlation) instead of reimplementing it.\n *\n * Implementation note: the npm test mock and CDN runtime share this module.\n * The mock reuses it over its own in-memory host so mocked handles resolve\n * from scripted state, but the published tarball never carries transport\n * logic — every helper here speaks through `sendEvent`/`subscribe` alone.\n */\n\n/** Debounce window for {@link BridgeOperations.markRead} reports. */\nconst READ_REPORT_DEBOUNCE_MS = 400;\n/** Default {@link BridgeOperations.startNewConversation} cooldown. */\nconst START_NEW_CONVERSATION_COOLDOWN_MS = 5_000;\n\n/**\n * The minimal client surface the operations layer builds on. Satisfied by the\n * real `BridgeClient` and by the npm test mock.\n */\nexport interface BridgeOperationsHost {\n\tgetState(): AppDisplayState | null;\n\tsubscribe(callback: () => void): () => void;\n\tsendEvent<K extends keyof AppEvents>(\n\t\tevent: K,\n\t\t...args: AppEvents[K] extends undefined ? [] : [payload: AppEvents[K]]\n\t): void;\n}\n\n/** Outcome of a correlated submit: accepted, or rejected with a message. */\nexport type OperationResult = { ok: true } | { ok: false; message: string };\n\nexport interface SettleOptions {\n\t/** Reject with an `Error` if the operation has not settled in time. */\n\ttimeoutMs?: number;\n}\n\n/**\n * Rejection every in-flight `settled()` promise receives when the client is\n * destroyed, so teardown is the layer's bound of last resort even without a\n * `timeoutMs`. The class identity does not survive the npm/CDN module split,\n * so detect it by `error.name === \"BridgeClientDestroyedError\"` (or via\n * {@link BridgeClientDestroyedError.is}), never by `instanceof` alone.\n */\nexport class BridgeClientDestroyedError extends Error {\n\tconstructor(message = \"The bridge client was destroyed.\") {\n\t\tsuper(message);\n\t\tthis.name = \"BridgeClientDestroyedError\";\n\t}\n\n\tstatic is(error: unknown): error is BridgeClientDestroyedError {\n\t\treturn (\n\t\t\terror instanceof Error && error.name === \"BridgeClientDestroyedError\"\n\t\t);\n\t}\n}\n\nexport interface SendHandle {\n\t/**\n\t * The uuid put on the wire as `tempMessageUuid`. Core seeds the optimistic\n\t * message's `clientKey` from it, so the sent message is findable in\n\t * `chat.messages` by `clientKey === tempMessageUuid`.\n\t */\n\treadonly tempMessageUuid: string;\n\t/**\n\t * Resolves with the message once it appears in `chat.messages` (the\n\t * optimistic row; the durable server echo keeps the same `clientKey`).\n\t * Rejects when core reports THIS send's error before the row appears —\n\t * rate limit, over-length, session not ready, dispatch failure —\n\t * correlated exactly on `chat.error.clientKey` matching the minted\n\t * `tempMessageUuid`, so an unrelated failure in the same window never\n\t * falsely rejects a delivered send. On a core document that predates\n\t * `chat.error.clientKey`, falls back to the positional\n\t * `chat.error.seq`-advance detection, and before that to `chat.error`\n\t * text transitions. A secret send NEVER resolves — core deliberately\n\t * writes no transcript row for it — so pass `timeoutMs` or skip\n\t * `settled()` for secret sends. Destroying the client rejects every\n\t * pending `settled()` with a {@link BridgeClientDestroyedError}.\n\t */\n\tsettled(opts?: SettleOptions): Promise<Message>;\n}\n\nexport interface CaptureHandle {\n\t/**\n\t * Resolves on the verdict for THIS capture submit: `{ ok: true }` when the\n\t * value was accepted (core removes the capture block), or\n\t * `{ ok: false, message }` when it was rejected — a server validation\n\t * rejection (observed on the `isSubmitting` falling edge, with the\n\t * validation error text), or a failure/drop core stamps on the capture's\n\t * `lastSubmitFailure` keyed to this submit's own minted `submitId` (no\n\t * session to round-trip, value over the length cap, transport failure, a\n\t * duplicate submit while one is in flight, a host-driven reset). The\n\t * correlation is exact: the handle never reads the global `chat.error`,\n\t * so an unrelated failure in the same window can never masquerade as this\n\t * capture's verdict. On a core document that predates `lastSubmitFailure`,\n\t * those failures are not capture-attributable at all and the handle stays\n\t * pending — pass `timeoutMs` when targeting older cores.\n\t */\n\tsettled(opts?: SettleOptions): Promise<OperationResult>;\n}\n\nexport interface CsatSubmitHandle {\n\t/**\n\t * Resolves when core reports this survey's outcome, correlated on the\n\t * success seq advance x surveyType x conversationId triple (and the\n\t * mirror-image error triple). A `partial` submit reports no terminal\n\t * outcome, so `settled()` on one only settles via `timeoutMs`.\n\t */\n\tsettled(opts?: SettleOptions): Promise<OperationResult>;\n}\n\nexport interface FileUploadHandle {\n\t/** The minted upload id sent with `file.upload.start`. */\n\treadonly uploadId: string;\n\t/**\n\t * Re-send the EXACT same `{ file, uploadId }` pair. Core correlates the\n\t * retry to the failed attempt by that pair, so a fresh pair would orphan\n\t * the original error state.\n\t */\n\tretry(): void;\n\t/** Dismiss a reported upload error (`file.uploadError`). */\n\tdismissError(): void;\n}\n\nexport interface EndChatEligibilityResult {\n\t/** `csat.endChatEligible` at the moment the answer arrived. */\n\teligible: boolean | null;\n\t/** Which survey End Chat should present, when eligible. */\n\tsurveyTarget: \"bot\" | \"agent\" | null;\n}\n\nexport interface ObserveOptions<T> {\n\t/** Change detector; defaults to `Object.is`. */\n\tequals?: (a: T, b: T) => boolean;\n\t/** Also invoke the callback immediately with the current value. */\n\temitInitial?: boolean;\n}\n\n/**\n * Typed helpers over the bridge event/state contract. Obtain via\n * `client.operations`. Fire-and-forget helpers return `void`; correlated\n * helpers return a handle or Promise that settles from state transitions\n * (there is no request/response channel — core answers through state).\n */\nexport interface BridgeOperations {\n\t/**\n\t * Send a chat message. The body is trimmed; an empty result sends nothing\n\t * (the returned handle then rejects). `secret: true` sends a masked\n\t * message: the raw body never enters the transcript and the handle never\n\t * resolves. While `ui.secretMessage.isOpen` is true you likely want\n\t * `secret: true` to match the composer's mode.\n\t */\n\tsendMessage(body: string, opts?: { secret?: boolean }): SendHandle;\n\t/** Retry a failed send by message id (see {@link canRetryMessage}). */\n\tretryMessage(messageId: string): void;\n\t/**\n\t * Whether the retry affordance should show for a message: a `text`\n\t * message core marked `isRetryable`, no disconnected Zendesk SDK, and the\n\t * message belongs to the current conversation (or predates stamping).\n\t */\n\tcanRetryMessage(message: Message): boolean;\n\n\t/** Answer a quick-reply chip. Pass the chip and its message id verbatim. */\n\tselectQuickReply(reply: QuickReply, messageId: string): void;\n\t/** Answer an options block. The label falls back to the option id. */\n\tselectOption(option: OptionItem, messageId: string): void;\n\t/**\n\t * Submit the active selectable list (`conversation.stateData.listSelection`\n\t * supplies the `dataId`). No-op when no list selection is active.\n\t */\n\tsubmitListSelection(selectedIds: string[]): void;\n\t/**\n\t * Cancel the active selectable list. Uses the block's own cancel response\n\t * and label (`label` overrides the visible label). No-op when no list\n\t * selection is active or it has no cancel response.\n\t */\n\tcancelListSelection(label?: string): void;\n\t/**\n\t * Submit the active capture field (`conversation.stateData.capture`\n\t * supplies the `dataId`). Each call mints its own `submitId`, so the\n\t * handle settles only on ITS OWN submit's verdict or failure — a second\n\t * submit sent while one is in flight is dropped by core and its handle\n\t * resolves `{ ok: false }` on its own terms rather than inheriting the\n\t * in-flight submit's verdict.\n\t */\n\tsubmitCapture(value: string): CaptureHandle;\n\t/**\n\t * Cancel the active capture field. `label` overrides the block's own\n\t * cancel label. No-op when no capture is active or it has no cancel\n\t * response.\n\t */\n\tcancelCapture(label?: string): void;\n\n\t/** React to a bot message with a thumbs vote. */\n\taddReaction(messageId: string, reaction: 1 | -1): void;\n\t/**\n\t * Report that the user activated a `link` message. Core resolves the\n\t * stored message for tracking and, with `navigateInHost`, validated\n\t * same-tab host navigation.\n\t */\n\treportLinkClick(messageId: string, opts?: { navigateInHost?: boolean }): void;\n\n\t/**\n\t * Advance the persisted read watermark. Reports are debounced 400 ms, the\n\t * watermark is a monotonic lexical max over `_id` cursor strings (calling\n\t * with an older cursor never lowers it), and the accumulator resets when\n\t * `chat.conversationId` changes so a pending cursor from conversation A\n\t * can never commit against B. Call with `message.cursor` for each message\n\t * that scrolls into view; messages stamped with a DIFFERENT\n\t * `conversationId` must be excluded by the caller (the transcript\n\t * preserves the previous conversation's rows).\n\t */\n\tmarkRead(cursor: string, opts?: { conversationId?: string }): void;\n\n\t/**\n\t * Request an older page of history. No-op unless `chat.canLoadMore` is\n\t * true and no load is already in flight.\n\t * Sends `chat.logs.loadMore`. After a failed page, Core ignores requests\n\t * during a delay for that conversation without setting `chat.loadingPreviousMessages`.\n\t * The delay starts at 1 second and doubles after each failure, up to 30 seconds.\n\t * Retry on a new user scroll, not when the loading flag clears.\n\t * When optional `chat.previousMessagesLoadSeq` rises, you can request the next page.\n\t * It counts successful older-history pages, including pages with no visible rows.\n\t */\n\tloadOlderMessages(): void;\n\t/**\n\t * Invoke `callback` each time restored/baseline history (re)loads\n\t * (`chat.recentMessagesLoadSeq` advances) — the one-shot trigger for\n\t * \"scroll to the bottom of restored history\" and for seeding an unread\n\t * divider. Returns an unsubscribe function.\n\t */\n\tonHistoryLoaded(callback: (seq: number) => void): () => void;\n\n\t/**\n\t * Submit a CSAT survey. Pass `conversationId: message.conversationId` for\n\t * a survey rendered IN the transcript (the row can outlive its\n\t * conversation); omit it — or pass `null`, which is treated as omitted on\n\t * the wire and in correlation — for End Chat / proactive flows, where\n\t * core resolves the conversation itself.\n\t */\n\tsubmitCsat(\n\t\tdata: CsatSubmitData,\n\t\topts: {\n\t\t\tsurveyType: string;\n\t\t\tpartial?: boolean;\n\t\t\tconversationId?: string | null;\n\t\t},\n\t): CsatSubmitHandle;\n\t/**\n\t * Analytics: report that a survey was displayed. Fire-and-forget. Core drops\n\t * it for post-agent survey types (`live_chat`): api records that survey as\n\t * shown when it routes its row into the transcript, so a client record would\n\t * count the same prompt twice.\n\t */\n\ttrackCsatShown(surveyType: string, conversationId?: string | null): void;\n\n\t/**\n\t * Ask core whether End Chat should present a survey. Correlated by\n\t * latching `csat.endChatEligibility.seq` and resolving when it advances\n\t * (there is no request id; a `ready -> ready` re-check changes no other\n\t * observable state).\n\t */\n\tcheckEndChatEligibility(\n\t\topts?: SettleOptions,\n\t): Promise<EndChatEligibilityResult>;\n\t/**\n\t * End the conversation now with no survey. On a bot with\n\t * `config.features.endLiveChat` this is also the End Chat that follows a live\n\t * chat (`liveAgent.liveChatEndedInConversation` is true): call it straight\n\t * from the End Chat control — no confirmation, no `checkEndChatEligibility()`,\n\t * no survey — as legacy `END_LIVE_CHAT` does. While such a bot is still in a\n\t * live chat, call `endLiveChat()` instead.\n\t */\n\tskipCsatAndEndChat(): void;\n\t/**\n\t * End only the live-agent leg (legacy `END_LIVE_CHAT`). On a bot with\n\t * `config.features.endLiveChat`, an End Chat confirmed while\n\t * `liveAgent.inLiveChat` MUST use this rather than `skipCsatAndEndChat()` or\n\t * an `end_chat` `submitCsat()`, and offer no end-chat survey: the conversation\n\t * stays open and api routes the post-agent (`live_chat`) survey as an inline\n\t * row once the leg has ended, whereas an end-chat submission there is stored\n\t * as the agent's rating under the wrong survey type. Core ignores it outside\n\t * a live-agent leg.\n\t */\n\tendLiveChat(): void;\n\t/**\n\t * Start a new conversation while preserving prior messages. Applies a local\n\t * cooldown (default 5 s); `false` means no request was sent, while `true`\n\t * means only that it was sent. Also returns false while\n\t * `app.conversationStartPending` is true, without extending the cooldown.\n\t * Proactive requests follow resetApp's shared restart behavior below.\n\t */\n\tstartNewConversation(opts?: { cooldownMs?: number }): boolean;\n\t/**\n\t * Clear the transcript and start a new conversation. If proactive requests\n\t * are already pending, this and startNewConversation wait for them: success\n\t * completes the restart with the proactive's messages and existing transcript\n\t * intact, without an extra greeting. A completed success awaiting conversation\n\t * delivery can satisfy one restart too; another explicit restart after that\n\t * completion is a new intent, allowing recovery when no conversation arrives.\n\t * If all fail and no newer conversation,\n\t * session or accepted user send has superseded the request, the latest action\n\t * accepted by Core runs once: resetApp clears history; startNewConversation preserves it. Render\n\t * both controls busy while `app.conversationStartPending` is true; it is\n\t * optional on older cores. This void helper still sends while pending.\n\t * An accepted user send or retry cancels the waiting restart; a rejected send\n\t * does not.\n\t * Without pending work or an unused success awaiting delivery, resets begin immediately.\n\t */\n\tresetApp(): void;\n\n\t/**\n\t * Report a content-free composer-changed edge (drives live-agent typing\n\t * indicators). The event is payload-free by contract — composer text must\n\t * never cross the frame boundary — and this helper suppresses it entirely\n\t * in secret mode (pass `secret: true`, or leave it to the\n\t * `ui.secretMessage.isOpen` state check): even a content-free typing edge\n\t * is a signal about the secret being typed.\n\t */\n\tnotifyComposerChanged(opts?: { secret?: boolean }): void;\n\t/** Open or close the masked (secret) composer mode. */\n\ttoggleSecretMessage(open: boolean): void;\n\n\t/**\n\t * Start a live-agent file upload. Mints and retains the `{ file,\n\t * uploadId }` pair so `retry()` replays exactly it. Progress and errors\n\t * land on `file.isUploading`, `file.transfer`, and `file.uploadError`\n\t * (core can report an error without ever setting `isUploading`).\n\t */\n\tstartFileUpload(file: File): FileUploadHandle;\n\n\t/** Change the conversation language (BCP 47 tag the bot offers). */\n\tsetLanguage(language: string): void;\n\t/**\n\t * Email the transcript. Resolves/rejects when `transcript.email.status`\n\t * reaches `success | error` with its `.seq` advanced past this call's\n\t * baseline — a request core drops without ever broadcasting `pending`\n\t * (a host-driven reset in progress) still rejects. On a core document\n\t * that predates the seq, falls back to the `pending -> terminal` edge.\n\t * The email is trimmed; an empty result rejects immediately without\n\t * sending (core drops an empty email silently, so nothing would ever\n\t * settle it). The request is single-flight and address-aware: a call for\n\t * the SAME address as an in-flight request joins it and settles on its\n\t * verdict; a call for a DIFFERENT address is rejected address-matched on\n\t * `transcript.email.conflict.*` — ONLY the conflicting call's promise\n\t * rejects, while the in-flight request still completes, writes the final\n\t * status, and settles its own handle with the true outcome (a rejection\n\t * here never means the in-flight email was not sent). On a core document\n\t * that predates the conflict channel, a conflicting call settles on the\n\t * in-flight verdict instead.\n\t */\n\temailTranscript(email: string, opts?: SettleOptions): Promise<void>;\n\t/**\n\t * Download the transcript. Same settle contract as\n\t * {@link BridgeOperations.emailTranscript}, on\n\t * `transcript.download.status`.\n\t */\n\tdownloadTranscript(opts?: SettleOptions): Promise<void>;\n\t/** Toggle the new-message alert sound. */\n\tsetAlertSound(enabled: boolean): void;\n\t/**\n\t * Ask the HOST page to request Web Notification permission (permission is\n\t * per-origin, so the widget frame's own value is not the governing one).\n\t * Result lands on `chatter.notificationPermission`.\n\t */\n\trequestNotificationPermission(): void;\n\t/** Set the user's in-widget theme override. */\n\tsetTheme(theme: \"light\" | \"dark\" | \"auto\"): void;\n\t/** Set the user's in-widget text-size override. */\n\tsetTextSize(size: \"small\" | \"default\" | \"large\"): void;\n\n\t/** The header close affordance (ends the chat when `endChat.canEnd`). */\n\tclose(): void;\n\t/** Minimize the widget. */\n\tminimize(): void;\n\t/** Dismiss the visible toast. */\n\tdismissToast(): void;\n\t/**\n\t * Clear `chat.error` in core. Clear core state rather than hiding the\n\t * error locally, or the session's second error renders nothing.\n\t */\n\tdismissError(): void;\n\t/** Ask the SDK host to expand or restore the app's viewport surface. */\n\tsetHostViewportExpanded(expanded: boolean): void;\n\t/** Replace floating custom-app frame styles. Pass {} to restore defaults. */\n\tsetFrameStyles(styles: CustomAppFrameStyles): void;\n\t/** Retry after a response outage banner. */\n\tretryAfterOutage(): void;\n\t/**\n\t * Report a fatal app error to core (renders the fallback UI and reports\n\t * telemetry). Callable from anywhere with a client reference — including\n\t * a class error boundary outside any React provider.\n\t */\n\treportAppError(error: unknown, componentStack?: string): void;\n}\n\nfunction mintUuid(): string {\n\tconst cryptoObj = typeof crypto === \"undefined\" ? undefined : crypto;\n\tif (typeof cryptoObj?.randomUUID === \"function\") {\n\t\treturn cryptoObj.randomUUID();\n\t}\n\tconst bytes = new Uint8Array(16);\n\tif (typeof cryptoObj?.getRandomValues === \"function\") {\n\t\tcryptoObj.getRandomValues(bytes);\n\t} else {\n\t\tfor (let index = 0; index < bytes.length; index += 1) {\n\t\t\tbytes[index] = Math.floor(Math.random() * 256);\n\t\t}\n\t}\n\t// RFC 4122 version/variant bits so core's uuid-shape validation accepts it.\n\tbytes[6] = ((bytes[6] as number) & 0x0f) | 0x40;\n\tbytes[8] = ((bytes[8] as number) & 0x3f) | 0x80;\n\tconst hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\"));\n\treturn `${hex.slice(0, 4).join(\"\")}-${hex.slice(4, 6).join(\"\")}-${hex\n\t\t.slice(6, 8)\n\t\t.join(\"\")}-${hex.slice(8, 10).join(\"\")}-${hex.slice(10).join(\"\")}`;\n}\n\ntype Outcome<T> = { value: T } | { error: Error };\n\n/**\n * The disposal hooks a settle promise registers with, so `dispose()` can\n * reject every in-flight `settled()` with a typed\n * {@link BridgeClientDestroyedError} — the layer's bound of last resort when\n * no `timeoutMs` was passed.\n */\ninterface SettleLifecycle {\n\tisDisposed(): boolean;\n\tregister(abort: (error: Error) => void): void;\n\tunregister(abort: (error: Error) => void): void;\n}\n\n/**\n * Settle a promise from state transitions: `inspect` runs once immediately\n * and then on every state update until it returns an outcome. `timeoutMs`\n * bounds the wait; without it an operation whose answer never arrives keeps\n * its subscription until it settles or the client is destroyed, which\n * rejects it with a {@link BridgeClientDestroyedError}.\n */\nfunction settleFromState<T>(\n\thost: BridgeOperationsHost,\n\tlifecycle: SettleLifecycle,\n\tinspect: (state: AppDisplayState | null) => Outcome<T> | undefined,\n\topts?: SettleOptions,\n): Promise<T> {\n\treturn new Promise<T>((resolve, reject) => {\n\t\tif (lifecycle.isDisposed()) {\n\t\t\treject(new BridgeClientDestroyedError());\n\t\t\treturn;\n\t\t}\n\t\tlet done = false;\n\t\tlet unsubscribe: (() => void) | null = null;\n\t\tlet timer: ReturnType<typeof setTimeout> | null = null;\n\t\tconst abort = (error: Error): void => {\n\t\t\tfinish({ error });\n\t\t};\n\t\tconst finish = (outcome: Outcome<T>): void => {\n\t\t\tif (done) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdone = true;\n\t\t\tlifecycle.unregister(abort);\n\t\t\tunsubscribe?.();\n\t\t\tif (timer !== null) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t}\n\t\t\tif (\"value\" in outcome) {\n\t\t\t\tresolve(outcome.value);\n\t\t\t} else {\n\t\t\t\treject(outcome.error);\n\t\t\t}\n\t\t};\n\t\tconst run = (): void => {\n\t\t\tconst outcome = inspect(host.getState());\n\t\t\tif (outcome) {\n\t\t\t\tfinish(outcome);\n\t\t\t}\n\t\t};\n\t\trun();\n\t\tif (done) {\n\t\t\treturn;\n\t\t}\n\t\tlifecycle.register(abort);\n\t\tunsubscribe = host.subscribe(run);\n\t\tif (opts?.timeoutMs !== undefined) {\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\tfinish({\n\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t`Bridge operation timed out after ${opts.timeoutMs}ms.`,\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t}, opts.timeoutMs);\n\t\t}\n\t});\n}\n\ninterface StateObservation {\n\tsubscribeKey<K extends keyof AppDisplayState>(\n\t\tkey: K,\n\t\tcallback: (\n\t\t\tvalue: AppDisplayState[K] | undefined,\n\t\t\tprevious: AppDisplayState[K] | undefined,\n\t\t) => void,\n\t\topts?: ObserveOptions<AppDisplayState[K] | undefined>,\n\t): () => void;\n\tselect<T>(\n\t\tselector: (state: AppDisplayState | null) => T,\n\t\tcallback: (value: T, previous: T | undefined) => void,\n\t\topts?: ObserveOptions<T>,\n\t): () => void;\n}\n\n/**\n * Build the `subscribeKey`/`select` observation primitives over a host.\n * `Object.is` is a sound default equality for `subscribeKey`: state crosses\n * the frame boundary by structured clone, so the real client restores each\n * structurally unchanged key's previous reference before notifying (the test\n * mock shares references in memory to begin with). For `select`, whose\n * selector typically MINTS a new object per call, pass a custom `equals`\n * (or select a primitive/signature string) when projecting objects.\n */\nexport function createStateObservation(\n\thost: BridgeOperationsHost,\n): StateObservation {\n\tfunction select<T>(\n\t\tselector: (state: AppDisplayState | null) => T,\n\t\tcallback: (value: T, previous: T | undefined) => void,\n\t\topts?: ObserveOptions<T>,\n\t): () => void {\n\t\tconst equals = opts?.equals ?? Object.is;\n\t\tlet previous = selector(host.getState());\n\t\tif (opts?.emitInitial === true) {\n\t\t\tcallback(previous, undefined);\n\t\t}\n\t\treturn host.subscribe(() => {\n\t\t\tconst next = selector(host.getState());\n\t\t\tif (equals(next, previous)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst before = previous;\n\t\t\tprevious = next;\n\t\t\tcallback(next, before);\n\t\t});\n\t}\n\n\tfunction subscribeKey<K extends keyof AppDisplayState>(\n\t\tkey: K,\n\t\tcallback: (\n\t\t\tvalue: AppDisplayState[K] | undefined,\n\t\t\tprevious: AppDisplayState[K] | undefined,\n\t\t) => void,\n\t\topts?: ObserveOptions<AppDisplayState[K] | undefined>,\n\t): () => void {\n\t\treturn select((state) => state?.[key], callback, opts);\n\t}\n\n\treturn { subscribeKey, select };\n}\n\nexport interface BridgeOperationsBundle {\n\toperations: BridgeOperations;\n\t/** Release the layer's own subscriptions and timers (client teardown). */\n\tdispose(): void;\n}\n\nexport function createBridgeOperations(\n\trawHost: BridgeOperationsHost,\n): BridgeOperationsBundle {\n\tlet disposed = false;\n\n\t// Disposal must actually stop the layer: after `dispose()` no helper may\n\t// send another event or take a new subscription (the client's transport\n\t// is gone). Guarding the host once covers every helper below.\n\tconst host: BridgeOperationsHost = {\n\t\tgetState: () => rawHost.getState(),\n\t\tsubscribe: (callback) =>\n\t\t\tdisposed\n\t\t\t\t? () => {\n\t\t\t\t\t\t// nothing was subscribed\n\t\t\t\t\t}\n\t\t\t\t: rawHost.subscribe(callback),\n\t\tsendEvent: (event, ...args) => {\n\t\t\tif (!disposed) {\n\t\t\t\trawHost.sendEvent(event, ...args);\n\t\t\t}\n\t\t},\n\t};\n\n\tconst liveSettleAborts = new Set<(error: Error) => void>();\n\tconst lifecycle: SettleLifecycle = {\n\t\tisDisposed: () => disposed,\n\t\tregister: (abort) => {\n\t\t\tliveSettleAborts.add(abort);\n\t\t},\n\t\tunregister: (abort) => {\n\t\t\tliveSettleAborts.delete(abort);\n\t\t},\n\t};\n\n\t// markRead accumulator (mirrors the reference app's useReadTracking).\n\tlet readMax = \"\";\n\tlet readReported = \"\";\n\tlet readTimer: ReturnType<typeof setTimeout> | null = null;\n\tlet readConversationId: string | null = null;\n\tlet readResetUnsubscribe: (() => void) | null = null;\n\n\tfunction resetReadAccumulator(): void {\n\t\treadMax = \"\";\n\t\treadReported = \"\";\n\t\tif (readTimer !== null) {\n\t\t\tclearTimeout(readTimer);\n\t\t\treadTimer = null;\n\t\t}\n\t}\n\n\t// Cursor spaces are per conversation: when the pin changes, drop the\n\t// accumulator AND any pending debounced report so a cursor collected in\n\t// conversation A can never be committed against B.\n\tfunction ensureReadResetSubscription(): void {\n\t\tif (readResetUnsubscribe) {\n\t\t\treturn;\n\t\t}\n\t\tlet previousPin = host.getState()?.[\"chat.conversationId\"] ?? null;\n\t\treadResetUnsubscribe = host.subscribe(() => {\n\t\t\tconst pin = host.getState()?.[\"chat.conversationId\"] ?? null;\n\t\t\tif (pin !== previousPin) {\n\t\t\t\tpreviousPin = pin;\n\t\t\t\tresetReadAccumulator();\n\t\t\t\treadConversationId = pin;\n\t\t\t}\n\t\t});\n\t}\n\n\tfunction scheduleReadReport(): void {\n\t\tif (readTimer !== null) {\n\t\t\treturn;\n\t\t}\n\t\treadTimer = setTimeout(() => {\n\t\t\treadTimer = null;\n\t\t\tif (readMax > readReported) {\n\t\t\t\treadReported = readMax;\n\t\t\t\thost.sendEvent(\"chat.messages.read\", {\n\t\t\t\t\tcursor: readMax,\n\t\t\t\t\t...(readConversationId ? { conversationId: readConversationId } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t}, READ_REPORT_DEBOUNCE_MS);\n\t}\n\n\tlet newConversationBlockedUntil = 0;\n\n\tconst operations: BridgeOperations = {\n\t\tsendMessage(body, opts) {\n\t\t\tconst trimmed = typeof body === \"string\" ? body.trim() : \"\";\n\t\t\tconst secret = opts?.secret === true;\n\t\t\tconst tempMessageUuid = mintUuid();\n\t\t\tlet errorBaseline: string | null = null;\n\t\t\tlet errorSeqBaseline: number | null = null;\n\t\t\tif (trimmed) {\n\t\t\t\tconst preSend = host.getState();\n\t\t\t\terrorBaseline = preSend?.[\"chat.error\"] ?? null;\n\t\t\t\terrorSeqBaseline = preSend?.[\"chat.error.seq\"] ?? null;\n\t\t\t\thost.sendEvent(\"chat.message.send\", {\n\t\t\t\t\tbody: trimmed,\n\t\t\t\t\tmessageType: secret ? \"secret\" : \"text\",\n\t\t\t\t\ttempMessageUuid,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttempMessageUuid,\n\t\t\t\tsettled(settleOpts) {\n\t\t\t\t\tif (!trimmed) {\n\t\t\t\t\t\treturn Promise.reject(\n\t\t\t\t\t\t\tnew Error(\"sendMessage requires a non-empty body.\"),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (secret) {\n\t\t\t\t\t\t// No optimistic row exists for a secret send by design, so\n\t\t\t\t\t\t// there is nothing to resolve with. Time-bound or skip; the\n\t\t\t\t\t\t// wait is still bounded by client destroy.\n\t\t\t\t\t\treturn settleFromState<Message>(\n\t\t\t\t\t\t\thost,\n\t\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn settleFromState<Message>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst messages = state?.[\"chat.messages\"] ?? [];\n\t\t\t\t\t\t\tconst match = messages.find(\n\t\t\t\t\t\t\t\t(message) => message.clientKey === tempMessageUuid,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\treturn { value: match };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst error = state?.[\"chat.error\"] ?? null;\n\t\t\t\t\t\t\t// A core that stamps `chat.error.clientKey` (always present,\n\t\t\t\t\t\t\t// null when unattributed) attributes every pre-row send\n\t\t\t\t\t\t\t// failure to the send's own key, so the correlation is exact:\n\t\t\t\t\t\t\t// only THIS send's error rejects, and an unrelated failure\n\t\t\t\t\t\t\t// landing between the send and its optimistic row (the seq\n\t\t\t\t\t\t\t// fallback's false-rejection window) is ignored.\n\t\t\t\t\t\t\tif (state !== null && \"chat.error.clientKey\" in state) {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\terror !== null &&\n\t\t\t\t\t\t\t\t\tstate[\"chat.error.clientKey\"] === tempMessageUuid\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst errorSeq = state?.[\"chat.error.seq\"];\n\t\t\t\t\t\t\tif (typeof errorSeq === \"number\") {\n\t\t\t\t\t\t\t\tif (errorSeqBaseline === null) {\n\t\t\t\t\t\t\t\t\t// The send raced state hydration (no state yet when the\n\t\t\t\t\t\t\t\t\t// baseline was captured): latch the first observed seq\n\t\t\t\t\t\t\t\t\t// and treat the current error as pre-existing.\n\t\t\t\t\t\t\t\t\terrorSeqBaseline = errorSeq;\n\t\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// The seq advances on EVERY error set — including a repeat\n\t\t\t\t\t\t\t\t// of the identical text, which the fallback below cannot\n\t\t\t\t\t\t\t\t// see. `!==` rather than `>`, so a counter restarted by a\n\t\t\t\t\t\t\t\t// full core reset still rejects.\n\t\t\t\t\t\t\t\tif (error !== null && errorSeq !== errorSeqBaseline) {\n\t\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Core document predates chat.error.seq: fall back to text\n\t\t\t\t\t\t\t// transitions (a repeated identical error is invisible here).\n\t\t\t\t\t\t\tif (error === null) {\n\t\t\t\t\t\t\t\terrorBaseline = null;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (error !== errorBaseline) {\n\t\t\t\t\t\t\t\treturn { error: new Error(error) };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tretryMessage(messageId) {\n\t\t\thost.sendEvent(\"chat.message.retry\", {\n\t\t\t\tmessage: { id: messageId, type: \"text\" },\n\t\t\t});\n\t\t},\n\n\t\tcanRetryMessage(message) {\n\t\t\tconst state = host.getState();\n\t\t\treturn (\n\t\t\t\tmessage.type === \"text\" &&\n\t\t\t\tmessage.isRetryable === true &&\n\t\t\t\tstate?.[\"zendesk.sdk.disconnected\"] !== true &&\n\t\t\t\t(message.conversationId == null ||\n\t\t\t\t\tmessage.conversationId === (state?.[\"chat.conversationId\"] ?? null))\n\t\t\t);\n\t\t},\n\n\t\tselectQuickReply(reply, messageId) {\n\t\t\thost.sendEvent(\"chat.quickReply.select\", {\n\t\t\t\tlabel: reply.label,\n\t\t\t\ttarget: reply.target,\n\t\t\t\tbuttonType: reply.buttonType,\n\t\t\t\t...(reply.value !== undefined ? { value: reply.value } : {}),\n\t\t\t\tmessageId,\n\t\t\t});\n\t\t},\n\n\t\tselectOption(option, messageId) {\n\t\t\thost.sendEvent(\"chat.option.select\", {\n\t\t\t\toptionId: option.id,\n\t\t\t\toptionLabel: option.label || option.id,\n\t\t\t\tmessageId,\n\t\t\t});\n\t\t},\n\n\t\tsubmitListSelection(selectedIds) {\n\t\t\tconst listSelection =\n\t\t\t\thost.getState()?.[\"conversation.stateData.listSelection\"];\n\t\t\tif (!listSelection) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"chat.selectableList.submit\", {\n\t\t\t\tselectedIds,\n\t\t\t\tdataId: listSelection.dataId,\n\t\t\t});\n\t\t},\n\n\t\tcancelListSelection(label) {\n\t\t\tconst listSelection =\n\t\t\t\thost.getState()?.[\"conversation.stateData.listSelection\"];\n\t\t\t// Core drops a cancel without a response id, so don't send one.\n\t\t\tif (!listSelection?.cancelCaptureResponseId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"listSelection.cancel\", {\n\t\t\t\tcancelResponseId: listSelection.cancelCaptureResponseId,\n\t\t\t\tcancelLabel: label ?? listSelection.cancelCaptureLabel,\n\t\t\t\tdataId: listSelection.dataId,\n\t\t\t});\n\t\t},\n\n\t\tsubmitCapture(value) {\n\t\t\tconst capture = host.getState()?.[\"conversation.stateData.capture\"];\n\t\t\tif (!capture) {\n\t\t\t\treturn {\n\t\t\t\t\tsettled: () =>\n\t\t\t\t\t\tPromise.reject(new Error(\"No capture field is active.\")),\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst dataId = capture.dataId;\n\t\t\tconst submitId = mintUuid();\n\t\t\thost.sendEvent(\"capture.submit\", { value, dataId, submitId });\n\t\t\t// The verdict rides the capture object itself: `isSubmitting` is\n\t\t\t// core-owned, flips true while the submit is in flight, and the\n\t\t\t// falling edge carries `validationErrorMessage`; an ACCEPTED value\n\t\t\t// removes the whole object (or replaces it with the next block).\n\t\t\t// Every failure or drop WITHOUT a server validation verdict — a\n\t\t\t// pre-flight drop that raises and settles `isSubmitting` inside one\n\t\t\t// broadcast batch, a transport failure, a duplicate submit dropped\n\t\t\t// while another is in flight — lands on `lastSubmitFailure` keyed\n\t\t\t// with this call's own `submitId`, so the correlation is exact and\n\t\t\t// the global `chat.error` is never consulted.\n\t\t\tlet sawSubmitting = false;\n\t\t\treturn {\n\t\t\t\tsettled: (settleOpts) =>\n\t\t\t\t\tsettleFromState<OperationResult>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst current = state?.[\"conversation.stateData.capture\"] ?? null;\n\t\t\t\t\t\t\tif (!current || current.dataId !== dataId) {\n\t\t\t\t\t\t\t\treturn { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Keyed failure BEFORE the in-flight latch: a duplicate\n\t\t\t\t\t\t\t// submit's drop is stamped while the OTHER submit still holds\n\t\t\t\t\t\t\t// `isSubmitting`, and latching that flag here would make this\n\t\t\t\t\t\t\t// handle inherit the other submit's verdict.\n\t\t\t\t\t\t\tconst failure = current.lastSubmitFailure;\n\t\t\t\t\t\t\tif (failure && failure.submitId === submitId) {\n\t\t\t\t\t\t\t\treturn { value: { ok: false, message: failure.message } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (current.isSubmitting === true) {\n\t\t\t\t\t\t\t\tsawSubmitting = true;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (sawSubmitting) {\n\t\t\t\t\t\t\t\treturn current.validationErrorMessage\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\t\t\t\t\t\tok: false,\n\t\t\t\t\t\t\t\t\t\t\t\tmessage: current.validationErrorMessage,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t),\n\t\t\t};\n\t\t},\n\n\t\tcancelCapture(label) {\n\t\t\tconst capture = host.getState()?.[\"conversation.stateData.capture\"];\n\t\t\tif (!capture?.cancelCaptureResponseId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"capture.cancel\", {\n\t\t\t\tcancelResponseId: capture.cancelCaptureResponseId,\n\t\t\t\tcancelLabel: label ?? capture.cancelCaptureLabel,\n\t\t\t\tdataId: capture.dataId,\n\t\t\t});\n\t\t},\n\n\t\taddReaction(messageId, reaction) {\n\t\t\thost.sendEvent(\"chat.message.reaction.add\", { messageId, reaction });\n\t\t},\n\n\t\treportLinkClick(messageId, opts) {\n\t\t\thost.sendEvent(\"chat.link.click\", {\n\t\t\t\tmessageId,\n\t\t\t\t...(opts?.navigateInHost !== undefined\n\t\t\t\t\t? { navigateInHost: opts.navigateInHost }\n\t\t\t\t\t: {}),\n\t\t\t});\n\t\t},\n\n\t\tmarkRead(cursor, opts) {\n\t\t\tif (typeof cursor !== \"string\" || cursor === \"\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tensureReadResetSubscription();\n\t\t\tconst conversationId =\n\t\t\t\topts?.conversationId ??\n\t\t\t\thost.getState()?.[\"chat.conversationId\"] ??\n\t\t\t\tnull;\n\t\t\tif (conversationId !== readConversationId) {\n\t\t\t\tresetReadAccumulator();\n\t\t\t\treadConversationId = conversationId;\n\t\t\t}\n\t\t\tif (cursor > readMax) {\n\t\t\t\treadMax = cursor;\n\t\t\t\tscheduleReadReport();\n\t\t\t}\n\t\t},\n\n\t\tloadOlderMessages() {\n\t\t\tconst state = host.getState();\n\t\t\tif (\n\t\t\t\tstate?.[\"chat.canLoadMore\"] !== true ||\n\t\t\t\tstate[\"chat.loadingPreviousMessages\"] === true\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"chat.logs.loadMore\");\n\t\t},\n\n\t\tonHistoryLoaded(callback) {\n\t\t\tlet previous = host.getState()?.[\"chat.recentMessagesLoadSeq\"] ?? 0;\n\t\t\treturn host.subscribe(() => {\n\t\t\t\tconst seq = host.getState()?.[\"chat.recentMessagesLoadSeq\"] ?? 0;\n\t\t\t\tif (seq > previous) {\n\t\t\t\t\tprevious = seq;\n\t\t\t\t\tcallback(seq);\n\t\t\t\t} else if (seq < previous) {\n\t\t\t\t\t// Returned to 0 on a chat clear; re-arm without firing.\n\t\t\t\t\tprevious = seq;\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tsubmitCsat(data, opts) {\n\t\t\tconst { surveyType, partial, conversationId } = opts;\n\t\t\tconst baselineSeq = host.getState()?.[\"csat.submitSuccess.seq\"] ?? 0;\n\t\t\tlet errorBaseline = host.getState()?.[\"csat.submitError\"] ?? null;\n\t\t\tlet errorSeqBaseline = host.getState()?.[\"csat.submitError.seq\"] ?? null;\n\t\t\t// `null` means \"omitted\" on the wire AND in correlation: core's\n\t\t\t// resolver treats a falsy id as absent and reports the SESSION's id\n\t\t\t// back, so a null-scoped predicate could never match it and the\n\t\t\t// handle would hang on a fully successful submit.\n\t\t\thost.sendEvent(\"csat.submit\", {\n\t\t\t\tdata,\n\t\t\t\tsurveyType,\n\t\t\t\t...(partial !== undefined ? { partial } : {}),\n\t\t\t\t...(conversationId != null ? { conversationId } : {}),\n\t\t\t});\n\t\t\t// When the caller omitted conversationId (End Chat / proactive),\n\t\t\t// core resolves the conversation itself, so match on the type +\n\t\t\t// seq alone; a caller-scoped survey matches its exact conversation.\n\t\t\tconst matchesConversation = (\n\t\t\t\tcandidate: string | null | undefined,\n\t\t\t): boolean =>\n\t\t\t\tconversationId == null || (candidate ?? null) === conversationId;\n\t\t\treturn {\n\t\t\t\tsettled: (settleOpts) =>\n\t\t\t\t\tsettleFromState<OperationResult>(\n\t\t\t\t\t\thost,\n\t\t\t\t\t\tlifecycle,\n\t\t\t\t\t\t(state) => {\n\t\t\t\t\t\t\tconst seq = state?.[\"csat.submitSuccess.seq\"] ?? 0;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tseq > baselineSeq &&\n\t\t\t\t\t\t\t\tstate?.[\"csat.submitSuccess.surveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\tmatchesConversation(\n\t\t\t\t\t\t\t\t\tstate?.[\"csat.submitSuccess.conversationId\"],\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn { value: { ok: true } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst error = state?.[\"csat.submitError\"] ?? null;\n\t\t\t\t\t\t\tconst errorSeq = state?.[\"csat.submitError.seq\"];\n\t\t\t\t\t\t\tif (typeof errorSeq === \"number\") {\n\t\t\t\t\t\t\t\t// The seq advances on EVERY error set — including a repeat\n\t\t\t\t\t\t\t\t// of the identical text (the same submit dropped twice at a\n\t\t\t\t\t\t\t\t// core gate), which the text transition below cannot see.\n\t\t\t\t\t\t\t\tif (errorSeqBaseline === null) {\n\t\t\t\t\t\t\t\t\t// The submit raced state hydration: latch the first\n\t\t\t\t\t\t\t\t\t// observed seq and treat the current error as\n\t\t\t\t\t\t\t\t\t// pre-existing.\n\t\t\t\t\t\t\t\t\terrorSeqBaseline = errorSeq;\n\t\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\terror !== null &&\n\t\t\t\t\t\t\t\t\terrorSeq !== errorSeqBaseline &&\n\t\t\t\t\t\t\t\t\tstate?.[\"csat.submitErrorSurveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\t\tmatchesConversation(state?.[\"csat.submitErrorConversationId\"])\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn { value: { ok: false, message: error } };\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Core document predates csat.submitError.seq: fall back to\n\t\t\t\t\t\t\t// text transitions.\n\t\t\t\t\t\t\tif (error === null) {\n\t\t\t\t\t\t\t\t// Core clears the error at the start of every\n\t\t\t\t\t\t\t\t// non-partial submit; from here any error is ours.\n\t\t\t\t\t\t\t\terrorBaseline = null;\n\t\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\terror !== errorBaseline &&\n\t\t\t\t\t\t\t\tstate?.[\"csat.submitErrorSurveyType\"] === surveyType &&\n\t\t\t\t\t\t\t\tmatchesConversation(state?.[\"csat.submitErrorConversationId\"])\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn { value: { ok: false, message: error } };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsettleOpts,\n\t\t\t\t\t),\n\t\t\t};\n\t\t},\n\n\t\ttrackCsatShown(surveyType, conversationId) {\n\t\t\thost.sendEvent(\"csat.shown\", {\n\t\t\t\tsurveyType,\n\t\t\t\t...(conversationId != null ? { conversationId } : {}),\n\t\t\t});\n\t\t},\n\n\t\tcheckEndChatEligibility(opts) {\n\t\t\tconst baselineSeq = host.getState()?.[\"csat.endChatEligibility.seq\"] ?? 0;\n\t\t\thost.sendEvent(\"csat.checkEndChatEligibility\");\n\t\t\treturn settleFromState<EndChatEligibilityResult>(\n\t\t\t\thost,\n\t\t\t\tlifecycle,\n\t\t\t\t(state) => {\n\t\t\t\t\tconst seq = state?.[\"csat.endChatEligibility.seq\"] ?? 0;\n\t\t\t\t\tif (seq <= baselineSeq) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\teligible: state?.[\"csat.endChatEligible\"] ?? null,\n\t\t\t\t\t\t\tsurveyTarget: state?.[\"csat.endChatSurveyTarget\"] ?? null,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\n\t\tskipCsatAndEndChat() {\n\t\t\thost.sendEvent(\"endChat.skipCsat\");\n\t\t},\n\n\t\tendLiveChat() {\n\t\t\thost.sendEvent(\"endChat.endLiveChat\");\n\t\t},\n\n\t\tstartNewConversation(opts) {\n\t\t\tif (host.getState()?.[\"app.conversationStartPending\"] === true) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst now = Date.now();\n\t\t\tif (now < newConversationBlockedUntil) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tnewConversationBlockedUntil =\n\t\t\t\tnow + (opts?.cooldownMs ?? START_NEW_CONVERSATION_COOLDOWN_MS);\n\t\t\thost.sendEvent(\"app.startNewConversation\");\n\t\t\treturn true;\n\t\t},\n\n\t\tresetApp() {\n\t\t\thost.sendEvent(\"app.reset\");\n\t\t},\n\n\t\tnotifyComposerChanged(opts) {\n\t\t\t// Even a content-free typing edge is a signal that a secret is\n\t\t\t// being typed, so it stays suppressed at the source.\n\t\t\tif (\n\t\t\t\topts?.secret === true ||\n\t\t\t\thost.getState()?.[\"ui.secretMessage.isOpen\"] === true\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thost.sendEvent(\"ui.composer.textChanged\");\n\t\t},\n\n\t\ttoggleSecretMessage(open) {\n\t\t\thost.sendEvent(\"ui.secretMessage.toggle\", { open });\n\t\t},\n\n\t\tstartFileUpload(file) {\n\t\t\tconst uploadId = mintUuid();\n\t\t\thost.sendEvent(\"file.upload.start\", { file, uploadId });\n\t\t\treturn {\n\t\t\t\tuploadId,\n\t\t\t\tretry() {\n\t\t\t\t\thost.sendEvent(\"file.upload.retry\", { file, uploadId });\n\t\t\t\t},\n\t\t\t\tdismissError() {\n\t\t\t\t\thost.sendEvent(\"file.upload.errorDismiss\");\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tsetLanguage(language) {\n\t\t\thost.sendEvent(\"settings.language.change\", { language });\n\t\t},\n\n\t\temailTranscript(email, opts) {\n\t\t\t// Core drops an empty email silently — no status transition ever —\n\t\t\t// so validate here and reject immediately instead of hanging.\n\t\t\tconst trimmed = typeof email === \"string\" ? email.trim() : \"\";\n\t\t\tif (!trimmed) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(\"emailTranscript requires a non-empty email.\"),\n\t\t\t\t);\n\t\t\t}\n\t\t\thost.sendEvent(\"settings.transcript.email.send\", { email: trimmed });\n\t\t\treturn transcriptSettled(\"transcript.email.status\", opts, trimmed);\n\t\t},\n\n\t\tdownloadTranscript(opts) {\n\t\t\thost.sendEvent(\"settings.transcript.download.request\");\n\t\t\treturn transcriptSettled(\"transcript.download.status\", opts);\n\t\t},\n\n\t\tsetAlertSound(enabled) {\n\t\t\thost.sendEvent(\"settings.alertSound.toggle\", { enabled });\n\t\t},\n\n\t\trequestNotificationPermission() {\n\t\t\thost.sendEvent(\"settings.notifications.requestPermission\");\n\t\t},\n\n\t\tsetTheme(theme) {\n\t\t\thost.sendEvent(\"appearance.setTheme\", { theme });\n\t\t},\n\n\t\tsetTextSize(size) {\n\t\t\thost.sendEvent(\"appearance.setTextSize\", { textSize: size });\n\t\t},\n\n\t\tclose() {\n\t\t\thost.sendEvent(\"ui.closeButton.click\");\n\t\t},\n\n\t\tminimize() {\n\t\t\thost.sendEvent(\"ui.minimizeButton.click\");\n\t\t},\n\n\t\tdismissToast() {\n\t\t\thost.sendEvent(\"ui.toast.dismiss\");\n\t\t},\n\n\t\tdismissError() {\n\t\t\thost.sendEvent(\"chat.error.dismiss\");\n\t\t},\n\n\t\tsetHostViewportExpanded(expanded) {\n\t\t\thost.sendEvent(\"ui.hostViewport.set\", { expanded });\n\t\t},\n\n\t\tsetFrameStyles(styles) {\n\t\t\thost.sendEvent(\"ui.frameStyles.set\", { styles });\n\t\t},\n\n\t\tretryAfterOutage() {\n\t\t\thost.sendEvent(\"outage.retry\");\n\t\t},\n\n\t\treportAppError(error, componentStack) {\n\t\t\tconst err = error instanceof Error ? error : null;\n\t\t\thost.sendEvent(\"app.error.report\", {\n\t\t\t\tmessage: err?.message ?? String(error),\n\t\t\t\tstack: err?.stack ?? \"\",\n\t\t\t\tcomponentStack: componentStack ?? \"\",\n\t\t\t});\n\t\t},\n\t};\n\n\tfunction transcriptSettled(\n\t\tkey: \"transcript.email.status\" | \"transcript.download.status\",\n\t\topts?: SettleOptions,\n\t\t// Email only: reject THIS call when core rejects a conflicting request\n\t\t// for exactly this address on `transcript.email.conflict.*` — the\n\t\t// shared status channel never carries the conflict, so the in-flight\n\t\t// caller settles on its own verdict.\n\t\tconflictEmail?: string,\n\t): Promise<void> {\n\t\tconst seqKey = `${key}.seq` as\n\t\t\t| \"transcript.email.status.seq\"\n\t\t\t| \"transcript.download.status.seq\";\n\t\tlet previous = host.getState()?.[key] ?? \"idle\";\n\t\tlet seqBaseline = host.getState()?.[seqKey] ?? null;\n\t\tlet conflictSeqBaseline =\n\t\t\thost.getState()?.[\"transcript.email.conflict.seq\"] ?? null;\n\t\treturn settleFromState<void>(\n\t\t\thost,\n\t\t\tlifecycle,\n\t\t\t(state) => {\n\t\t\t\tif (conflictEmail !== undefined) {\n\t\t\t\t\tconst conflictSeq = state?.[\"transcript.email.conflict.seq\"];\n\t\t\t\t\tif (typeof conflictSeq === \"number\") {\n\t\t\t\t\t\tif (conflictSeqBaseline === null) {\n\t\t\t\t\t\t\t// The request raced state hydration: latch the first\n\t\t\t\t\t\t\t// observed seq, mirroring the status-seq baseline below.\n\t\t\t\t\t\t\t// Residual: if that first snapshot already carries THIS\n\t\t\t\t\t\t\t// call's own conflict, the reject branch never fires and\n\t\t\t\t\t\t\t// the handle falls through to the shared-status\n\t\t\t\t\t\t\t// correlation — the same bounded degradation as the\n\t\t\t\t\t\t\t// documented old-core (no conflict keys) skew. Not\n\t\t\t\t\t\t\t// closable from here: with no pre-call baseline, a\n\t\t\t\t\t\t\t// matching-address conflict in the first snapshot is\n\t\t\t\t\t\t\t// indistinguishable from a pre-existing one, and\n\t\t\t\t\t\t\t// treating it as ours would false-reject.\n\t\t\t\t\t\t\tconflictSeqBaseline = conflictSeq;\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tconflictSeq !== conflictSeqBaseline &&\n\t\t\t\t\t\t\tstate?.[\"transcript.email.conflict.email\"] === conflictEmail\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\t\t\"emailTranscript was rejected: a request for a different \" +\n\t\t\t\t\t\t\t\t\t\t\"address is already in flight. The in-flight email \" +\n\t\t\t\t\t\t\t\t\t\t\"still completes with its own outcome.\",\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst status = state?.[key] ?? \"idle\";\n\t\t\t\tconst seq = state?.[seqKey];\n\t\t\t\tif (typeof seq === \"number\") {\n\t\t\t\t\t// The seq advances on every status write, so a request core\n\t\t\t\t\t// drops straight to a terminal status (never `pending` in a\n\t\t\t\t\t// broadcast snapshot) and a repeat of the previous terminal\n\t\t\t\t\t// status both stay observable.\n\t\t\t\t\tif (seqBaseline === null) {\n\t\t\t\t\t\t// The request raced state hydration: latch the first observed\n\t\t\t\t\t\t// seq and treat the current status as pre-existing.\n\t\t\t\t\t\tseqBaseline = seq;\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tif (seq === seqBaseline) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tif (status === \"success\") {\n\t\t\t\t\t\treturn { value: undefined };\n\t\t\t\t\t}\n\t\t\t\t\tif (status === \"error\") {\n\t\t\t\t\t\treturn { error: new Error(`${key} reported an error.`) };\n\t\t\t\t\t}\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\t// Core document predates the seq key: fall back to the\n\t\t\t\t// pending -> terminal edge.\n\t\t\t\tconst was = previous;\n\t\t\t\tprevious = status;\n\t\t\t\tif (was !== \"pending\") {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tif (status === \"success\") {\n\t\t\t\t\treturn { value: undefined };\n\t\t\t\t}\n\t\t\t\tif (status === \"error\") {\n\t\t\t\t\treturn { error: new Error(`${key} reported an error.`) };\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n\n\treturn {\n\t\toperations,\n\t\tdispose() {\n\t\t\tif (disposed) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdisposed = true;\n\t\t\tresetReadAccumulator();\n\t\t\treadResetUnsubscribe?.();\n\t\t\treadResetUnsubscribe = null;\n\t\t\t// Reject every in-flight settled() promise: the transport is gone,\n\t\t\t// so no state transition can ever answer it. Snapshot first — each\n\t\t\t// abort unregisters itself from the live set.\n\t\t\tfor (const abort of [...liveSettleAborts]) {\n\t\t\t\tabort(new BridgeClientDestroyedError());\n\t\t\t}\n\t\t\tliveSettleAborts.clear();\n\t\t},\n\t};\n}\n","import type { BridgeClient } from \"../contract/bridge-client\";\nimport {\n\tcreateBridgeOperations,\n\tcreateStateObservation,\n} from \"../contract/operations\";\nimport type { AppDisplayState, AppEvents } from \"../contract/types\";\n\n/** One recorded `sendEvent` call. */\nexport interface SentBridgeEvent<K extends keyof AppEvents = keyof AppEvents> {\n\tevent: K;\n\tpayload: AppEvents[K] | undefined;\n}\n\n/**\n * A scriptable, in-memory {@link BridgeClient} for unit tests.\n *\n * No postMessage, no origin checks, no network: tests drive state through\n * `setState`/`updateState` and assert on the events the component under test\n * sent through `sentEvents`. The `operations`, `subscribeKey`, and `select`\n * surfaces are the REAL implementations running over this in-memory state:\n * every operation records its events in `sentEvents`, and a correlated\n * handle (`sendMessage().settled()`, `submitCsat().settled()`, ...) resolves\n * when the test scripts the answering state transition via `updateState`.\n */\nexport interface MockBridgeClient extends BridgeClient {\n\t/** Replace the state snapshot and notify subscribers. */\n\tsetState(state: AppDisplayState | null): void;\n\t/**\n\t * Shallow-merge a partial snapshot over the current state (or over an empty\n\t * object when no state is set) and notify subscribers. Seeds\n\t * `\"chat.error.clientKey\": null` when the key is absent — production core\n\t * always publishes it (null when no keyed send owns the error), and\n\t * `operations` send handles take the keyed correlation path only when it is\n\t * present. Script a `chat.error` fixture with the send's `tempMessageUuid`\n\t * as `chat.error.clientKey` to reject that handle; use `setState` to script\n\t * an older core document without the key.\n\t */\n\tupdateState(partial: Partial<AppDisplayState>): void;\n\t/**\n\t * Every event sent through the client, in order. Each read returns a fresh\n\t * copy, so a captured array survives `clearSentEvents()`.\n\t */\n\treadonly sentEvents: readonly SentBridgeEvent[];\n\t/** Forget recorded events without touching state or subscribers. */\n\tclearSentEvents(): void;\n\t/** Number of active subscribers. */\n\treadonly subscriberCount: number;\n\t/** True once `destroy()` has been called. */\n\treadonly destroyed: boolean;\n}\n\n/**\n * Create a {@link MockBridgeClient}, optionally seeded with an initial state\n * snapshot. Mirrors the real client's teardown semantics: after `destroy()`,\n * state is `null`, subscribers are cleared, `sendEvent` is a no-op, and every\n * in-flight `operations` `settled()` promise rejects with a\n * `BridgeClientDestroyedError`. Notification also mirrors the real client:\n * a throwing subscriber is caught and logged, never aborting the pass.\n *\n * The derivation helpers (`filterDisplayable`, `groupMessages`, `messageKey`,\n * `selectUnread`, ...) are runtime-only: this package types them on\n * `MessagingBridgeModule` but ships no implementation, so there is no mock\n * module to destructure them from. In unit tests, inject them into the code\n * under test (plain function parameters or props); to exercise the real\n * implementations, load the module with `loadMessagingBridge()` in an\n * integration test. Do not re-implement them — a copy drifts from the\n * runtime's edge cases.\n */\nexport function createMockBridgeClient(\n\tinitialState: AppDisplayState | null = null,\n): MockBridgeClient {\n\tlet state = initialState;\n\tlet destroyed = false;\n\tconst subscribers = new Set<() => void>();\n\tconst sentEvents: SentBridgeEvent[] = [];\n\n\tfunction notify(): void {\n\t\t// Mirrors the real client's notify(): one throwing subscriber must not\n\t\t// abort the pass (a settled() promise resolved from a later subscriber\n\t\t// would otherwise hang) or skip subscribers that unsubscribe mid-pass.\n\t\tfor (const callback of [...subscribers]) {\n\t\t\ttry {\n\t\t\t\tcallback();\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[AdaMessaging] Bridge subscriber threw:\", err);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction getState(): AppDisplayState | null {\n\t\treturn state;\n\t}\n\n\tfunction subscribe(callback: () => void): () => void {\n\t\tsubscribers.add(callback);\n\t\treturn () => {\n\t\t\tsubscribers.delete(callback);\n\t\t};\n\t}\n\n\tfunction sendEvent<K extends keyof AppEvents>(\n\t\tevent: K,\n\t\t...args: AppEvents[K] extends undefined ? [] : [payload: AppEvents[K]]\n\t): void {\n\t\tif (destroyed) {\n\t\t\treturn;\n\t\t}\n\t\tsentEvents.push({ event, payload: args[0] });\n\t}\n\n\tconst host = { getState, subscribe, sendEvent };\n\tconst observation = createStateObservation(host);\n\tconst operationsBundle = createBridgeOperations(host);\n\n\treturn {\n\t\tgetState,\n\t\tsubscribe,\n\t\tsendEvent,\n\t\tsubscribeKey: observation.subscribeKey,\n\t\tselect: observation.select,\n\t\toperations: operationsBundle.operations,\n\n\t\tdestroy(): void {\n\t\t\tdestroyed = true;\n\t\t\toperationsBundle.dispose();\n\t\t\tstate = null;\n\t\t\tsubscribers.clear();\n\t\t},\n\n\t\tsetState(next: AppDisplayState | null): void {\n\t\t\tstate = next;\n\t\t\tnotify();\n\t\t},\n\n\t\tupdateState(partial: Partial<AppDisplayState>): void {\n\t\t\t// Production core always publishes `chat.error.clientKey` (null when\n\t\t\t// unattributed), and `operations` feature-detects on its presence.\n\t\t\t// Seed it so a scripted failure exercises the keyed path production\n\t\t\t// takes; override it in `partial` (or use setState) to script an\n\t\t\t// older core document.\n\t\t\tstate = {\n\t\t\t\t\"chat.error.clientKey\": null,\n\t\t\t\t...(state ?? {}),\n\t\t\t\t...partial,\n\t\t\t} as AppDisplayState;\n\t\t\tnotify();\n\t\t},\n\n\t\tget sentEvents(): readonly SentBridgeEvent[] {\n\t\t\treturn [...sentEvents];\n\t\t},\n\n\t\tclearSentEvents(): void {\n\t\t\tsentEvents.length = 0;\n\t\t},\n\n\t\tget subscriberCount(): number {\n\t\t\treturn subscribers.size;\n\t\t},\n\n\t\tget destroyed(): boolean {\n\t\t\treturn destroyed;\n\t\t},\n\t};\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export declare const CUSTOM_APP_FALLBACK_CODES: readonly [
2
+ "origin_not_allowlisted",
3
+ "allowlist_unavailable",
4
+ "frame_load_failed",
5
+ "handshake_timeout",
6
+ ];
7
+ export type CustomAppFallbackCode = (typeof CUSTOM_APP_FALLBACK_CODES)[number];
8
+ export type FrameLength = number | `${number}%`;
9
+ export type FrameInset = FrameLength | "auto";
10
+ export interface CustomAppFrameStyles {
11
+ top?: FrameInset;
12
+ right?: FrameInset;
13
+ bottom?: FrameInset;
14
+ left?: FrameInset;
15
+ width?: FrameLength;
16
+ height?: FrameLength;
17
+ minWidth?: FrameLength;
18
+ minHeight?: FrameLength;
19
+ maxWidth?: FrameLength;
20
+ maxHeight?: FrameLength;
21
+ borderRadius?: number;
22
+ borderWidth?: number;
23
+ borderColor?: string;
24
+ backgroundColor?: string;
25
+ boxShadow?: string;
26
+ }
27
+ export declare function parseCustomAppFrameStyles(
28
+ value: unknown,
29
+ ): CustomAppFrameStyles | null;
@@ -1,2 +1,3 @@
1
1
  export type * from "./csat-settings.js";
2
2
  export type * from "./file-upload.js";
3
+ export type * from "./custom-app-frame-styles.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ada-cx/messaging-bridge",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "Types, test mocks, and a thin CDN loader for building a custom app UI on Ada Messaging's bridge contract. The bridge runtime always loads from Ada's CDN.",
5
5
  "type": "module",
6
6
  "license": "ISC",