Skip to content

Events

Everything aparté does that your app might care about goes out as a DOM event. There are 46 of them, and they are grouped below by the node they are dispatched on — because that is the part a listener gets wrong: attach to the element an event never reaches and it simply never fires, with no error to say why.

@aparte/core augments HTMLElementEventMap, so once the package is in your TypeScript program addEventListener types the detail for you, with no cast:

chat.addEventListener('aparte-send', (event) => {
event.detail.content; // string — typed, with no cast
});

The four wrappers surface six of these as callbacks in their own idiom (Wrapper surface); everything else is listened for on the DOM, in every framework alike.

Dispatched by the element itself and bubbling, so a listener on any ancestor — including document — receives them. This is where an intent lives: the user pressed something, and your app decides what it means.

EventDetailDispatched onFired by
aparte-actionAparteActionEventDetailelement<aparte-chat-bubble>
aparte-action-clickAparteActionClickEventDetailelement<aparte-composer-action>
aparte-approval-mode-changeAparteApprovalModeChangeEventDetailelement<aparte-approval-mode>
aparte-archive-conversationAparteConversationArchiveDetailelement<aparte-conversation-list>
aparte-attachment-previewAparteAttachmentPreviewEventDetailelement<aparte-chat-bubble>, <aparte-composer-attachments>
aparte-branch-navigateAparteBranchNavigateEventDetailelement<aparte-chat-bubble>
aparte-cancelelement<aparte-composer>
aparte-composer-changeAparteComposerChangeEventDetailelement<aparte-composer>
aparte-composer-submitelement<aparte-composer-input>
aparte-context-thresholdAparteContextThresholdEventDetailelement<aparte-context>
aparte-delete-conversationAparteConversationDeleteDetailelement<aparte-conversation-list>
aparte-editAparteEditEventDetailelement<aparte-chat-bubble>
aparte-feedbackAparteFeedbackEventDetailelement<aparte-chat-bubble>
aparte-link-clickAparteLinkClickEventDetailelement<aparte-chat-bubble>
aparte-message-infoAparteMessageInfoEventDetailelement<aparte-chat-bubble>
aparte-model-changeAparteModelChangeEventDetailelement<aparte-model-selector>
aparte-optgroup-toggleAparteOptgroupToggleEventDetailelement<aparte-optgroup>
aparte-path-changedApartePathChangedEventDetailelement<aparte-chat-viewport>
aparte-pin-conversationAparteConversationPinDetailelement<aparte-conversation-list>
aparte-rename-conversationAparteConversationRenameDetailelement<aparte-conversation-list>
aparte-reset-doneelement<aparte-chat-viewport>
aparte-retryAparteRetryEventDetailelement<aparte-chat-bubble>
aparte-scroll-rail-jumpAparteScrollRailJumpDetailelement<aparte-scroll-rail>
aparte-segment-updateAparteSegmentUpdateEventDetailelement<aparte-chat-viewport>
aparte-select-changeAparteSelectChangeDetailelement<aparte-select>
aparte-select-closeelement<aparte-select>
aparte-select-conversationAparteConversationSelectDetailelement<aparte-conversation-list>
aparte-select-openelement<aparte-select>
aparte-sendAparteSendEventDetailelement<aparte-composer>
aparte-sidebar-toggleAparteSidebarToggleDetailelement<aparte-sidebar>
aparte-split-resizeAparteSplitResizeDetailelement<aparte-split>
aparte-suggestionAparteSuggestionEventDetailelement<aparte-suggestions>
aparte-unarchive-conversationAparteConversationArchiveDetailelement<aparte-conversation-list>
aparte-unpin-conversationAparteConversationPinDetailelement<aparte-conversation-list>

A custom action-bar button was pressed.

Bubbles up when the button is clicked, carrying which button it was and the composer it belongs to. The type argument is not decoration: a BARE @fires records CustomEvent with no argument, and the bindings generator then emits EventEmitter<void> with a listener that drops $event — so an Angular consumer with two custom buttons could not tell which one fired. detail: { actionId: string, composer: AparteComposer | null }

After a switch: { mode, previousMode }. Bubbles and crosses shadow roots.

The archive item was chosen on a live conversation.

An attached image was clicked, asking the app to open it full-size.

The ‹1/2› picker moved between sibling versions.

The stop button was pressed. No detail; the two window events below carry the target.

Any of value / streaming / disabled / attachments / panel changed, folded into one event.

A submitting Enter was pressed with no <aparte-composer> ancestor to submit to; with one it calls root.submit() and dispatches nothing. No detail — the host that placed this primitive reads getValue().

The level changed. Bubbles.

The delete was confirmed. Nothing is removed here.

An edit was saved.

Thumbs up or down.

A link in the message body is about to be followed. Cancelable: preventDefault() keeps the browser from navigating, so a host can route the link itself.

The info affordance was pressed.

The provider or the model changed; carries both ids.

The group was collapsed or expanded.

The active branch path changed, after a retry fork or a navigation.

The pin item was chosen on an unpinned row.

A rename was committed with a new, non-empty title. Nothing is renamed here.

clearAll() finished emptying the transcript. No detail.

Retry was pressed; the host forks the turn.

A tick was activated. Bubbles, cancelable: preventDefault() leaves the transcript where it is.

A segment grew or settled during a stream.

The selection changed; carries the new value, its label and the previous value.

The dropdown closed. No detail.

A row’s title was activated; the host loads that conversation.

The dropdown opened. No detail.

A message was submitted: the text, its attachments and the target.

The sidebar opened or closed, by a toggle, by Escape, by a click on the scrim or by collapsed being set. Bubbles.

The position settled: after a drag, a key, a double-click or a property set. Never during a drag — persist from here.

A starter was clicked. Bubbles, and is cancelable: preventDefault() stops the composer from being filled or submitted.

The same item on an already-archived one; same detail shape, opposite intent.

The same item on a pinned row; same detail shape, opposite intent.

The turn lifecycle, dispatched by AparteClient on the <aparte-chat> it drives — bubbling and composed, and stamped with targetId so two chats on one page stay apart. A composer treats an ABSENT targetId as “for me”, which is what lets a single-chat page work with no wiring at all.

EventDetailDispatched onFired by
aparte-message-abortedAparteMessageAbortedEventDetailchat host + window<aparte-composer>
aparte-message-doneAparteMessageDoneEventDetailchat hostAparteClient
aparte-message-errorAparteMessageErrorEventDetailchat hostAparteClient
aparte-message-startAparteMessageStartEventDetailchat hostAparteClient
aparte-tool-approval-requestAparteToolApprovalRequestDetailchat hostAparteClient

The run for this target ended early — the user pressed Stop, or abort() was called. This element dispatches it on window; AparteClient also dispatches it on the chat host, so it is listenable on either.

Detail payload for aparte-message-done. Dispatched when a turn finishes normally. Carries token usage when the provider reports it.

Detail payload for aparte-message-error. Dispatched when a turn fails. Four core components listen for it, and it is the event guides/troubleshooting.md tells you to bind to surface a failure.

Detail payload for aparte-message-start. Dispatched when a turn begins, before the first token. The composer and the conversation controller both use it to enter their streaming state.

Detail for aparte-tool-approval-request — emitted by the loop when a tool marked needsApproval is about to run. Listen for it to present the decision yourself. Core’s own gate asks at the COMPOSER (<aparte-elicitation> renders the panel there), and the transcript row shows only a state — there is no Approve/Reject control in a bubble. This block used to say “the built-in renderer already shows Approve/Reject inline”, which stopped being true when the decision moved to the composer. A consumer trusting it shipped with no presenter and no approvalResolver, and every needsApproval call aborted with nothing clickable on screen. The sibling docblock on needsApproval records the same correction; this one was missed in the same file.

Page-level operations, dispatched on window because they concern the whole document rather than one chat — by core, or by a plugin (the compaction events are @aparte/plugin-compaction’s; aparte-compact itself is the gauge’s). A listener on an element will never see these.

EventDetailDispatched onFired by
aparte-abortAparteAbortEventDetailwindow<aparte-composer>
aparte-compactAparteCompactEventDetailwindow<aparte-context>
aparte-compact-doneAparteCompactDoneEventDetailwindow@aparte/plugin-compaction
aparte-compact-errorAparteCompactErrorEventDetailwindow@aparte/plugin-compaction
aparte-compact-startAparteCompactStartEventDetailwindow@aparte/plugin-compaction
aparte-config-changeAparteConfigChangeEventDetailwindowAparteClient

Dispatched on window: stop the run for this target.

Dispatched on window when auto-compact is set and the gauge first reaches danger: compact this target’s transcript. Once per crossing, and only while the attribute is present. @aparte/plugin-compaction answers it; with no listener nothing happens.

Detail payload for aparte-compact-done. A union flattened to optional fields, because the two outcomes carry different payloads and a consumer cannot guess which: nothing to compact sends { skipped: true, reason }, a real compaction sends { summary, kept, dropped }.

Detail payload for aparte-compact-error. Note the asymmetry with aparte-message-error, which is deliberate rather than hidden: error here is a plain string, not an AparteError.

Detail payload for aparte-compact-start — the summarisation began. The point for a host to show a spinner: summarising a long conversation is a model call and takes as long as one.

Detail payload for aparte-config-change, dispatched on window whenever any provider, locale, action or model setting changes. Five core components listen for it — the widest listener footprint of any event that had no declared type. config is what makes per-instance config work: a component resolving to a different instance compares it and skips the rebuild instead of every chat on the page reacting to every config’s change.

The ones that travel the other way: core listens for these and never sends them, so they are COMMANDS your app issues rather than news it receives. Dispatch them on window; a listener will never see anything come back on the same name — the answer, when there is one, has a name of its own (aparte-reset is answered by aparte-reset-done).

EventDetailDispatched onFired by
aparte-resetwindowyour app

Empty every mounted transcript on the page. A command, not a notification: this is one of the events that travel the other way, dispatched by YOUR app on window and listened for here. Every connected <aparte-chat-viewport> clears its repository and its DOM and answers with aparte-reset-done, so a “New chat” button in a shell that holds no reference to the transcript still empties it. Carries no detail — and therefore no target, so it clears every viewport, not one.

aparte-cancel, aparte-composer-submit, aparte-reset, aparte-reset-done, aparte-select-close, aparte-select-open carry no detail, and are deliberately absent from AparteEventMap: a map entry would type event.detail as null and gain nothing. The name is the whole message.