Appearance
Events
Chat uses two distinct event channels:
- Bus ops — actor mailbox messages over NATS. See actor ops.
- DOM CustomEvents — UI signals that bubble up the component tree. This page documents those.
All Chat CustomEvents use bubbles: true, composed: true.
From <chat-app>
Class: MatrixChatApp. Declared static emits:
| Event | Detail | When |
|---|---|---|
app.connected | (none) | Bus connection established. |
app.disconnected | (none) | Bus connection lost. |
app.message-sent | (none) | A user message has been sent. |
app.stream-started | (none) | Streaming begins for the current prompt. |
app.stream-ended | (none) | Streaming completes (any phase: done/error/cancelled). |
These are pure status signals; they do not carry payloads.
From <conversation-surface>
Class: ConversationSurfaceActor. Declared static emits:
| Event | Detail | When |
|---|---|---|
conversation:target-changed | { mount } | The target actor was set or replaced. |
conversation:session-changed | { sessionId } | Session ID was assigned (after first prompt) or resumed. |
conversation:error | { message } | Surface-level error (rare; typically render errors). |
From <conversation-input>
Internal; bubbles through the surface root:
| Event | Detail | When |
|---|---|---|
conversation:submit | { text } | User pressed Enter. |
The surface listens at the shadow root and converts to a _handleSubmit(text) call.
From <conversation-session-list>
| Event | Detail | When |
|---|---|---|
conversation:session-selected | { sessionId } | User clicked a session entry. |
conversation:session-new | (none) | User clicked "New session". |
conversation:sessions-refresh-requested | (none) | User clicked refresh. |
The surface listens for these, calls _resumeSession, _handleNewSession, or _fetchSessionList, and dispatches state-changed events upward in turn.
From <chat-input>, <chat-message-list>, <chat-sidebar>, etc. (chat-app)
These are internal to the chat-app shell. The chat app itself has no canonical "external API" event vocabulary — its consumers are tightly coupled to its layout. The five app.* events above are the documented external surface.
If you embed chat-app inside another app, listen for those five events and ignore the internal traffic.
Capability prompt events
MatrixChatApp participates in the capability-realm flow:
| Event | Detail (subset) | When |
|---|---|---|
| (capability prompt) | { requestId, component, domain, requestedRight, mount, action } | The chat security realm wants user approval. |
| (capability decision) | { requestId, decision, approved, denied, ... } | User answered (approve/deny). |
These are dispatched by ChatSecurityRealmService and consumed by MatrixChatApp to render the prompt and forward the decision back.
Event listening in host shells
If you embed <chat-app>:
ts
chatApp.addEventListener('app.stream-started', () => updateUiBusyIndicator(true));
chatApp.addEventListener('app.stream-ended', () => updateUiBusyIndicator(false));If you embed <conversation-surface>:
ts
surface.addEventListener('conversation:session-changed', (e) => {
const sessionId = e.detail.sessionId;
persistSessionLocally(sessionId);
});composed: true means the event escapes shadow DOM, so listening at the host (parent of <conversation-surface>) works without piercing the shadow root.
Events that do NOT bubble out
A number of internal events are stopped at the surface boundary:
- The renderer's own internal events (e.g., scroll/expand) stay inside the renderer's shadow DOM.
- The session-list's internal click events are translated into the documented vocabulary.
If you need an event that's not in the documented list, the Chat package likely doesn't expose it, and embedding a different element directly is the wrong layer.
See also
Source:
projects/matrix-3/packages/chat/src/ChatApp.ts:184-190(app emits),chat-component/src/surface/ConversationSurfaceActor.ts:34-38(surface emits), individual surface element classes.