> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ada.cx/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ada.cx/_mcp/server.

# State and events

> **Note**
>
> Your handle's **Allowed websites** list controls custom apps. In your dashboard, go to **Channels** > **Messaging**. Before your transition, use **Channels** > **Chat**. Add your app's origin. Until the list allows your origin, a configured `appUrl` is dropped with a console warning and Ada's default app mounts. Local development needs no entry when both your app and the embedding page run on loopback hosts. See [Local development](/messaging/custom-apps/getting-started#local-development).

The bridge contract has two halves. Core publishes a **display state** object (`AppDisplayState`) that your app renders from. Your app sends typed **events** (`AppEvents`) back. The full typed contract ships in the npm package, so treat the package's TypeScript declarations as the shape reference.

This page documents when each customer-relevant key updates, which events are supported, and which surfaces are reserved. Prefer the [operations helpers](/messaging/custom-apps/operations) over raw events; nearly every supported event has one.

## Update cadence

Core coalesces state publication:

* At most **one state update per microtask**. A synchronous batch of internal changes produces one update, not one per change.
* The update is **skipped entirely** when the derived state is unchanged.
* Unchanged keys keep their **reference identity** across updates. State crosses the frame boundary by structured clone, so the bridge client restores the previous reference for each structurally unchanged key before it notifies. `Object.is` per key is therefore a sound change detector, which is what `subscribeKey` uses.
* An object key's identity changes exactly when its content does. For `chat.messages`, that is **every streaming delta**: the growing message body changes the array's content each time.

One consequence: a key that flips on and off inside one synchronous batch is invisible. `chat.isSending` behaves this way; see below.

## Display state

The tables below list the documented keys and the condition that updates each one. Keys not listed here are [reserved](#reserved-surfaces).

### Conversation keys

| Key                                    | Updates when                                                                                                                                                                                                                                                                                                                                                                                                  |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `app.conversationStartPending`         | True while Core resolves an App restart against proactive work, including a completed success awaiting conversation delivery. Core clears it when the request finishes or a newer conversation, session, or accepted user send replaces it. Router destruction also clears it. Optional on older Core versions; absent means false. This transient state does not acknowledge a completed conversation start. |
| `chat.answeredInteractiveIds`          | The end user answers an interactive block, or a later user message in the same conversation supersedes one. The set survives a reload. Filter interactive rows on it before enabling them.                                                                                                                                                                                                                    |
| `chat.canLoadMore`                     | A history load reports whether older pages remain.                                                                                                                                                                                                                                                                                                                                                            |
| `chat.composerText`                    | Core sets or clears its owned composer draft. Apply it only when its `revision` changes. Core clears it the moment a send consumes it.                                                                                                                                                                                                                                                                        |
| `chat.conversationGeneration`          | A conversation or reset boundary occurs; the counter is monotonic.                                                                                                                                                                                                                                                                                                                                            |
| `chat.conversationId`                  | The conversation is established server-side. The id arrives **asynchronously over the realtime channel**, not with the send response, and is null before then.                                                                                                                                                                                                                                                |
| `chat.error`                           | Core reports an error. A rejected send can leave an earlier send's sending and generating state unchanged. Core clears it when a send or retry starts, or when the service recovers after an outage. A new conversation, a full reset, or [`dismissError`](/messaging/custom-apps/operations#dismisserror) also clears it.                                                                                    |
| `chat.error.seq`                       | Core sets `chat.error` — the counter advances even when the new text is identical to the current one, which `chat.error` alone cannot surface. Send handles fall back to its advance on cores that predate `chat.error.clientKey`.                                                                                                                                                                            |
| `chat.error.clientKey`                 | Core sets `chat.error`. Carries the failing send's `tempMessageUuid` when the error belongs to one keyed send, and `null` otherwise. Send handles reject only when it matches their own key.                                                                                                                                                                                                                  |
| `chat.isConversationActive`            | The server's conversation-active verdict changes.                                                                                                                                                                                                                                                                                                                                                             |
| `chat.isGenerating`                    | True from the moment an optimistic user message lands until the reply arrives or the send fails. This, not `chat.isSending`, is the "a reply is coming" signal.                                                                                                                                                                                                                                               |
| `chat.isSending`                       | A send starts or completes. On a normal AI Agent send, both happen in one synchronous batch, so the true value is effectively never observable. It stays true only across a genuine failure gap.                                                                                                                                                                                                              |
| `chat.lastReadCursor`                  | A read report commits (after the 400 ms [`markRead`](/messaging/custom-apps/operations#markread) debounce), or the persisted watermark rehydrates on reload. Not monotonic across tabs by design.                                                                                                                                                                                                             |
| `chat.loadingPreviousMessages`         | A history page load starts or finishes. Core does not set this flag when it ignores a request during the delay after a failed page. Retry on a new end-user scroll, not when this flag clears. See [`loadOlderMessages`](/messaging/custom-apps/operations#loadoldermessages).                                                                                                                                |
| `chat.messages`                        | Any append, upsert, streaming delta, reaction, or history load. An optimistic user row appears immediately on send; its durable echo later reconciles in place, swapping `id` while carrying `clientKey` forward.                                                                                                                                                                                             |
| `chat.messages.reactions`              | Core adds a reaction, restores reactions from history, rolls back a reaction, or clears reactions.                                                                                                                                                                                                                                                                                                            |
| `chat.playbookStepExecuting`           | A Playbook step starts or finishes running server-side. While true, the composer must refuse to send: free text would be consumed as the step's input.                                                                                                                                                                                                                                                        |
| `chat.previousMessagesLoadSeq`         | A successful older-history page increments this counter, including a page with no visible rows. Failures do not increment it. When it rises, request the next page if `chat.canLoadMore` is true. Returns to 0 when the chat is cleared. Optional on older Core versions; absent means 0.                                                                                                                     |
| `chat.recentMessagesLoadSeq`           | Restored or baseline history (re)loads; the counter increments. Returns to 0 when the chat is cleared. Drives the one-shot "land at the bottom of restored history" behavior.                                                                                                                                                                                                                                 |
| `conversation.stateData.capture`       | The AI Agent blocks on a capture field. `isSubmitting` flips true when a submit leaves the browser and false when it resolves; `validationErrorMessage` carries the verdict. An accepted value removes the whole object. `lastSubmitFailure` records a submit that failed or was dropped without a server verdict, keyed by the submitter's own `submitId`.                                                   |
| `conversation.stateData.listSelection` | A list-selection block becomes active or resolves. Carries the `dataId` and cancel response that submit and cancel need.                                                                                                                                                                                                                                                                                      |

Render both restart controls as busy while `app.conversationStartPending` is true, keeping the transcript visible. Read it through `useBridgeState()` or subscribe with `client.subscribeKey("app.conversationStartPending", callback)`. Keep the controls busy until Core clears it, including after the local start-new-conversation cooldown expires. The typed constant is `STATE.APP_CONVERSATION_START_PENDING`.

### Agent and typing keys

| Key                                     | Updates when                                                                                                                                                                                               |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent.activeAgent`                     | An agent becomes active, changes, or leaves. Prefer a message's own `agentInfo` when naming its sender: restored history outlives an agent's session.                                                      |
| `liveAgent.inLiveChat`                  | A live-agent handoff connects or ends.                                                                                                                                                                     |
| `liveAgent.inPending`                   | A handoff request enters or leaves the pending state.                                                                                                                                                      |
| `liveAgent.liveChatEndedInConversation` | A live chat in this conversation ends. The next live chat, a reset, or a new conversation clears it. Reloads do not preserve it.                                                                           |
| `liveAgent.queueCancelPending`          | A queue-cancel request ([`ui.queueCancel.request`](#preference-file-and-chrome-events)) goes in flight or resolves. Disable your cancel affordance while true.                                             |
| `liveAgent.queuePosition`               | The end user's queue position changes.                                                                                                                                                                     |
| `ui.botTyping.visible`                  | The AI Agent's own typing indicator turns on or off (a request is in flight).                                                                                                                              |
| `ui.typingIndicator.visible`            | Core's combined typing verdict changes: the AI Agent is typing, or any agent is. Drive one indicator from this, or use the [`isAgentTyping`](/messaging/custom-apps/operations#derivation-helpers) helper. |

### Composer keys

| Key                              | Updates when                                                                                       |
| -------------------------------- | -------------------------------------------------------------------------------------------------- |
| `ui.secretMessage.buttonEnabled` | Whether to offer the masked-input toggle changes.                                                  |
| `ui.secretMessage.isOpen`        | Masked composer mode opens or closes. While true, send with `sendMessage(body, { secret: true })`. |

### Survey and End Chat keys

Survey success and terminal failure carry a signal, a survey type, and a conversation ID. Eligibility carries a latched sequence. The [operations helpers](/messaging/custom-apps/operations#surveys-and-end-chat) implement these patterns. Inline failures match by `inlineAnswerId` alone.

| Key                                 | Updates when                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `csat.agentSettings`                | Human-agent survey settings load or change.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `csat.botSettings`                  | AI Agent survey settings load or change.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `csat.endChatEligibility.seq`       | An eligibility answer arrives; the counter increments. There is no request id: latch the value before asking and treat yourself as pending until it advances.                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `csat.endChatEligibility.status`    | An eligibility check starts (`loading`) or answers (`ready`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `csat.endChatEligible`              | The eligibility answer arrives.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `csat.endChatSurveyTarget`          | The eligibility answer names which survey End Chat should present.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `csat.settings`                     | Core selects AI Agent or human-agent survey settings according to handoff state.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `csat.submitError`                  | Only a failure with `uiComplete: true` and no `inlineAnswerId` sets the error text and both scope keys. Only a valid write with that same condition clears them. Silent failures preserve the text, sequence, and both scope keys.                                                                                                                                                                                                                                                                                                                                                                                                     |
| `csat.submitError.seq`              | Only terminal failures advance this counter, including repeated failures with identical text. Silent failures and error clears do not advance it. Match its advance, survey type, and conversation ID. For inline rollback, match the first answer's `inlineAnswerId` against the `csat.inlineSubmitErrorIds` list. It holds the 200 most recent failed IDs, independent of conversation and survey type. Clear the local answer and pending full-survey cache only for that failure, without error text. Disable this rollback before a follow-up save; its failure must preserve the modal and answers. A second tap can then retry. |
| `csat.submitErrorConversationId`    | Together with `csat.submitError`; scopes the failure.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `csat.submitErrorSurveyType`        | Together with `csat.submitError`; scopes the failure.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `csat.submitSuccess.conversationId` | Together with the success counter; scopes the success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `csat.submitSuccess.seq`            | A survey write succeeds with `uiComplete: true`, which defaults to `!partial`, including after a conversation boundary. `submit` and `endConversation` do not control this counter. A first-answer write with `submit: true, uiComplete: false` does not advance it. Match the survey type and conversation ID.                                                                                                                                                                                                                                                                                                                        |
| `csat.submitSuccess.surveyType`     | Together with the success counter; scopes the success.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `endChat.canEnd`                    | Whether End Chat is offered changes: the conversation has not ended, the end user has sent in it, and a conversation id exists. Gates the header close affordance.                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |

### Transcript and file keys

| Key                               | Updates when                                                                                                                                                                                                |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `file.isUploading`                | A live-agent upload goes in flight or resolves. Core can reject a selection and report an error **without ever setting this true**, so watch `file.uploadError` too.                                        |
| `file.transfer`                   | Upload progress advances.                                                                                                                                                                                   |
| `file.uploadEnabled`              | Whether the file upload control is enabled changes. Use this with `file.uploadVisible` to control access to `startFileUpload`.                                                                              |
| `file.uploadError`                | An upload fails or its error is dismissed.                                                                                                                                                                  |
| `file.uploadVisible`              | Whether the file upload control is visible changes. Use this with `file.uploadEnabled` to control access to `startFileUpload`.                                                                              |
| `transcript.download.status`      | A transcript download moves `idle` to `pending` to `success` or `error`. A request that core drops during a host-driven reset skips `pending` and lands on `error` directly.                                |
| `transcript.download.status.seq`  | Every `transcript.download.status` write; the counter increments. Correlate on its advance to a terminal status, not on the standing text.                                                                  |
| `transcript.email.status`         | Same lifecycle, for the email-transcript request.                                                                                                                                                           |
| `transcript.email.status.seq`     | Same contract as `transcript.download.status.seq`, for the email request.                                                                                                                                   |
| `transcript.email.conflict.email` | Core rejects an `emailTranscript` call for a different address while one is in flight. This key carries the rejected address, so only that caller's promise rejects. The in-flight request is not affected. |
| `transcript.email.conflict.seq`   | Every `transcript.email.conflict.email` write; the counter increments.                                                                                                                                      |

### Configuration and preference keys

`config.*` keys carry the AI Agent's resolved configuration and theming: name, avatar, colors, languages, and feature toggles. They change rarely after startup. Five preference keys respond to the settings helpers:

| Key                         | Updates when                                                                                                                                                                                         |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `appearance.hostViewport`   | The host viewport changes. Contains `{ width, height }` in rounded CSS pixels when desktop floating frame styles can apply. It is `null` before the first update and when frame styles do not apply. |
| `appearance.userTextSize`   | The end user's text-size override changes ([`setTextSize`](/messaging/custom-apps/operations#settextsize)); core persists it.                                                                        |
| `appearance.userTheme`      | The end user's theme override changes ([`setTheme`](/messaging/custom-apps/operations#settheme)); core persists it.                                                                                  |
| `chatter.alertSoundEnabled` | The alert-sound preference changes, including after `setAlertSound`.                                                                                                                                 |
| `chatter.language`          | The conversation language changes, including after `setLanguage`.                                                                                                                                    |

`appearance.hostViewport.width` measures the host page's layout viewport. Its `height` measures the visible viewport.
Use these values to choose frame sizes at your app's breakpoints. Call [`setFrameStyles`](/messaging/custom-apps/operations#setframestyles) again when they change.
Inline embeds, mobile browsers, and native WebViews receive `null`.

The host page's Web Notification permission is mirrored into the state under a legacy-named prefix. It updates when the SDK host reports the permission, and after [`requestNotificationPermission`](/messaging/custom-apps/operations#requestnotificationpermission) resolves the prompt. Permission is per-origin, so your frame's own value is not the governing one:

```ts
client.subscribeKey("chatter.notificationPermission", (permission) => {
  // "default" | "granted" | "denied"
});
```

## Connectivity

`outage.connectivityLost` is the **sanctioned connectivity signal**. It is core's own verdict, and it updates when core decides connectivity was lost or restored. Use it to disable send UI or to warn before End Chat. The [`isConnectivityLost`](/messaging/custom-apps/operations#derivation-helpers) helper reads it.

Do not substitute other signals:

* `navigator.onLine` reports false positives on VPN and virtual-adapter transitions. Ada's reference app explicitly refuses it for this reason.
* Other `outage.*` keys are dismissible banner state and backoff internals, and they are [reserved](#reserved-surfaces). A dismissed banner can read as recovered while the outage continues.

## Events

Nearly every supported event has an [operations helper](/messaging/custom-apps/operations) that carries its guards and correlation logic. Send raw events only for the ones marked "send directly", or when you need something a helper does not expose.

### Lifecycle events

| Event                      | Operations helper      | Notes                                                                                                                                                                                      |
| -------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `app.error.report`         | `reportAppError`       | Renders the fallback UI and records telemetry.                                                                                                                                             |
| `app.initialize`           | none (send directly)   | The **required** mount handshake. Your app must load and send `app.initialize` within 15 seconds of frame mounting. This includes document load time. Otherwise, Core unmounts your frame. |
| `app.reset`                | `resetApp`             |                                                                                                                                                                                            |
| `app.startNewConversation` | `startNewConversation` | The helper refuses during its cooldown or a published pending restart.                                                                                                                     |

### Conversation events

| Event                           | Operations helper     | Notes                                                                                                                                                                                                                                                                                                                                                                                         |
| ------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `capture.cancel`                | `cancelCapture`       | The helper supplies the block's cancel response and `dataId`.                                                                                                                                                                                                                                                                                                                                 |
| `capture.submit`                | `submitCapture`       | The helper supplies the `dataId` and correlates the verdict.                                                                                                                                                                                                                                                                                                                                  |
| `chat.error.dismiss`            | `dismissError`        | Clears `chat.error` in core.                                                                                                                                                                                                                                                                                                                                                                  |
| `chat.link.click`               | `reportLinkClick`     |                                                                                                                                                                                                                                                                                                                                                                                               |
| `chat.logs.loadMore`            | `loadOlderMessages`   | The helper adds the in-flight guard.                                                                                                                                                                                                                                                                                                                                                          |
| `chat.message.reaction.add`     | `addReaction`         |                                                                                                                                                                                                                                                                                                                                                                                               |
| `chat.message.retry`            | `retryMessage`        | Core reads only the message id.                                                                                                                                                                                                                                                                                                                                                               |
| `chat.message.send`             | `sendMessage`         | When `tempMessageUuid` is an RFC 4122 uuid, core seeds the optimistic message's `clientKey` from it, so the sender can find its own send in `chat.messages`. `messageType: "secret"` masks the send, and no correlatable row exists.                                                                                                                                                          |
| `chat.messages.read`            | `markRead`            | The helper adds the debounce, the monotonic watermark, and the per-conversation reset.                                                                                                                                                                                                                                                                                                        |
| `chat.option.select`            | `selectOption`        |                                                                                                                                                                                                                                                                                                                                                                                               |
| `chat.quickReply.select`        | `selectQuickReply`    |                                                                                                                                                                                                                                                                                                                                                                                               |
| `chat.selectableList.submit`    | `submitListSelection` | The helper supplies the `dataId`.                                                                                                                                                                                                                                                                                                                                                             |
| `chat.widget.carouselLinkClick` | none (send directly)  | Send it when a `widget` message's carousel reports a link activation, with `actionType` set to `link` or `web_window` and `url` set to a link that a message can carry: the same link-protocol policy, including the Agent's allowed protocols, within 2048 characters. Core records a carousel link click for that message and drops a click whose `actionType` or `url` fails those checks. |
| `listSelection.cancel`          | `cancelListSelection` | Core drops a cancel without a response id; the helper guards this.                                                                                                                                                                                                                                                                                                                            |

### Survey and End Chat events

| Event                          | Operations helper         | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `csat.checkEndChatEligibility` | `checkEndChatEligibility` | The helper latches the sequence and resolves on its advance.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `csat.close`                   | none, send directly       | Send `{ conversationId: message.conversationId ?? null, surveyType: message.surveyType }` when the full survey closes, including when the row has no ID. Core ends the current surveyed conversation only for `answer_flow` and `end_chat`, without a survey write or `ada:close_chat`. Other survey types, including `live_chat` and `proactive`, keep the conversation open. For `null`, core uses the saved target from the last unstamped answer in the same generation. If a saved target belongs to an older generation, core ignores Close. Only when no saved target exists does core resolve the survey's captured or held conversation. Core ignores a close that does not resolve to the current conversation. |
| `csat.shown`                   | `trackCsatShown`          | Fire-and-forget analytics.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `csat.submit`                  | `submitCsat`              | Pass `conversationId` for transcript surveys; omit it for End Chat and proactive flows. `submit` controls the API flag. Successful non-partial POSTs with `submit: true` emit `ada:csat_submitted`. `uiComplete` controls completion results and defaults to `!partial`. `endConversation: "inline"` or `"endChat"` reports the completed surface. Core ends the current surveyed conversation after successful UI completion only for `answer_flow` and `end_chat`. Other survey types, including `live_chat`, keep the conversation open. When a raw event omits both `submit` and `uiComplete`, core applies the helper's [defaults](/messaging/custom-apps/operations#submitcsat).                                    |
| `endChat.confirm`              | none (send directly)      | Send it every time the end user confirms End Chat. Core publishes `ada:close_chat` once, at that point, and not again when `endChat.skipCsat` or an End Chat `csat.submit` ends that attempt. Without it, core publishes the event when the conversation ends. A confirm covers one attempt. The next confirm, or a message sent afterward, starts a new one. `csat.checkEndChatEligibility` does not start a new attempt, so send it before or after the confirm.                                                                                                                                                                                                                                                        |
| `endChat.endLiveChat`          | `endLiveChat`             | Ends an active or pending human-agent handoff while keeping the conversation open.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `endChat.skipCsat`             | `skipCsatAndEndChat`      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

### Preference, file, and chrome events

| Event                                      | Operations helper                                                    | Notes                                                                                                                                                                                            |
| ------------------------------------------ | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `appearance.setTextSize`                   | `setTextSize`                                                        |                                                                                                                                                                                                  |
| `appearance.setTheme`                      | `setTheme`                                                           |                                                                                                                                                                                                  |
| `file.upload.errorDismiss`                 | `startFileUpload` handle's `dismissError()`                          |                                                                                                                                                                                                  |
| `file.upload.retry`                        | `startFileUpload` handle's `retry()`                                 | Must replay the exact retained `{ file, uploadId }` pair.                                                                                                                                        |
| `file.upload.start`                        | `startFileUpload`                                                    |                                                                                                                                                                                                  |
| `outage.retry`                             | `retryAfterOutage`                                                   |                                                                                                                                                                                                  |
| `settings.alertSound.toggle`               | `setAlertSound`                                                      |                                                                                                                                                                                                  |
| `settings.language.change`                 | `setLanguage`                                                        |                                                                                                                                                                                                  |
| `settings.notifications.requestPermission` | `requestNotificationPermission`                                      |                                                                                                                                                                                                  |
| `settings.transcript.download.request`     | `downloadTranscript`                                                 |                                                                                                                                                                                                  |
| `settings.transcript.email.send`           | `emailTranscript`                                                    |                                                                                                                                                                                                  |
| `ui.closeButton.click`                     | `close`                                                              |                                                                                                                                                                                                  |
| `ui.composer.textChanged`                  | `notifyComposerChanged`                                              | Payload-free by contract; the helper suppresses it in secret mode.                                                                                                                               |
| `ui.frameStyles.set`                       | [`setFrameStyles`](/messaging/custom-apps/operations#setframestyles) | Payload: `{ styles: CustomAppFrameStyles }`. Replaces the outer frame's position, size, and appearance in desktop floating layouts. `{ styles: {} }` restores defaults. Other layouts ignore it. |
| `ui.hostViewport.set`                      | `setHostViewportExpanded`                                            |                                                                                                                                                                                                  |
| `ui.minimizeButton.click`                  | `minimize`                                                           |                                                                                                                                                                                                  |
| `ui.queueCancel.request`                   | none (send directly)                                                 | Leaves a live-chat queue. Payload-free. Gate the affordance on `liveAgent.inPending`, and disable it while `liveAgent.queueCancelPending` is true.                                               |
| `ui.secretMessage.toggle`                  | `toggleSecretMessage`                                                |                                                                                                                                                                                                  |
| `ui.toast.dismiss`                         | `dismissToast`                                                       |                                                                                                                                                                                                  |

## Reserved surfaces

Any key or event this page does not list is **reserved**: it exists for Ada's own app, native SDKs, or internal integrations, and it can change or disappear without notice. Do not build on reserved surfaces. This includes:

* `security.*` keys and events (challenge state and control).
* `voice.*` keys and events. These are annotated as reserved in the package types.
* `zendesk.*` and `zendeskMessaging.*` keys and events (handoff SDK control).
* `liveAgent.*` **events** (`liveAgent.handoff.request`, `liveAgent.message.send`, and the rest). Ada's own app never sends them; handoffs are driven by core. To leave a queue, send [`ui.queueCancel.request`](#preference-file-and-chrome-events). The documented `liveAgent.*` **state keys** above are read-only signals.
* `saml.*` keys.
* `outage.*` keys other than `outage.connectivityLost`, and any backoff internals.
* `image.print.request`.

The reserved surfaces still appear in the package's TypeScript declarations, because the types mirror the full wire contract. Presence in the types is not a support commitment; this page is.