Chat Web Components
Classification: API reference with partial embedding examples. The HTML and JavaScript blocks assume a hosted child where the chat element shell is available, a valid session UUID or agent start token, and application-owned mount and teardown code.
The Wippy chat UI is available as composable custom elements in contexts
where the Host injects the chat shell. A srcdoc iframe child can embed live
chat by tag without Vue imports or registration. The elements use the same chat
components and ChatTransport → SessionManager data layer as the host.
These are host-provided elements to consume. Unlike a
Web Component that you build yourself, you do not author
or register them. The srcdoc iframe injector makes them available by tag. The
Web Fragment gateway in the pinned Framework release deliberately omits
chat.js, so a Fragment page cannot assume these tags exist; use the host chat
controls there instead (see How they load).
Use these when you want a chat surface inside your own page or panel. To open the host's own chat panel imperatively instead, use
host.startChat(token)/host.openSession(sessionUUID)from@wippy-fe/proxy(see Proxy API).
The elements
| Tag | Renders | Key attributes | Events |
|---|---|---|---|
<wippy-chat> |
Full chat — header + messages + input | session-id, start-token, agent, show-selector, hide-header |
session-started, error |
<wippy-chat-messages> |
Message list only | session-id |
— |
<wippy-chat-input> |
Composer only | session-id |
— |
<wippy-session-selector> |
Session picker | active-session-id |
select |
Every element also accepts two per-instance theming attributes — custom-css and css-variables — covered in Theming.
How they load
The chat elements ship like
<wippy-loading>: a small
@wippy-fe/chat.js shell auto-registers all four tags. The srcdoc iframe
injector includes it in the host scripts array alongside loading.js and
proxy.js, so iframe-delivered pages do not install a package or call
customElements.define().
The Framework's Web Fragment gateway injects loading.js and
proxy-fragment.js, but not chat.js. Fragment-delivered pages should use
host.startChat() or host.openSession() unless a later platform contract
adds an explicit chat-shell opt-in. Direct web components mounted in the host
document must likewise not assume that another child realm registered the tags.
The implementation dependencies are code-split into a separate chat-internals.[hash].js chunk and lazy-loaded on first mount. While the chunk downloads, the element shows a <wippy-loading> placeholder; if the load fails it shows <wippy-error>. Pages that never mount a chat tag do not load the internals.
<wippy-chat>
Reactive session control requires Web Host 1.0.51 or newer. The element shell
is a Host-injected asset rather than a public @wippy-fe/chat package; older
Host releases only support the initial mount reliably.
The full chat surface: header, scrollable message list, and composer.
| Attribute | Type | Default | Description |
|---|---|---|---|
session-id |
string | — | Render this existing session (a session UUID). |
start-token |
string | — | Agent start token; starts a new session on mount when no session-id is set. |
agent |
string | — | Agent name (or title) to pre-select in the empty state, shown when no session is open. |
show-selector |
boolean | inherited | Inherited visibility is the inverse of hideSessionSelector. Default false without config. |
allow-select-model |
boolean | inherited, then true | Override model selection for this instance. |
hide-header |
boolean | false |
Hide the agent/model header bar (for compact embeds). |
Events (dispatched as CustomEvents on the element; read event.detail):
| Event | detail |
When |
|---|---|---|
session-started |
{ sessionId: string } |
A session is started — from start-token on mount, or by user action. |
error |
{ message: string } |
Session initialization fails (e.g. an invalid start-token). |
<!-- Start a new session from an agent start token -->
<wippy-chat start-token="agent-start-token" agent="researcher"></wippy-chat>
<!-- Pin an existing session -->
<wippy-chat session-id="019eb2ae-1234-5678-abcd-ef1234567890"></wippy-chat>
<!-- Built-in selector, no header bar -->
<wippy-chat show-selector hide-header></wippy-chat>
document.querySelector('wippy-chat')
.addEventListener('session-started', (e) => {
console.log('session:', e.detail.sessionId)
})
Reactive control without remounting
Keep one <wippy-chat> element mounted and update its attributes. A changed
session-id opens that session in place. Setting session-id="" or removing a
previously controlled attribute is an explicit New Chat transition: it
clears both the pinned and shared active session. An element that never had a
session-id remains selector-driven instead; absence on first mount is not a
clear command.
When a start-token is present, clearing session-id starts from that token
again. Changing the token also starts in place. The element consumes a token
once per custom-element host, so reconnecting or moving the same element does
not replay a live start. If a newer token, controlled session, manual selection,
or disconnect supersedes an in-flight start, the stale result cannot replace
the current session; any late-created session is closed.
const chat = document.querySelector('wippy-chat')
chat.setAttribute('session-id', existingSessionId)
// New Chat with an agent. No element replacement is required.
chat.setAttribute('start-token', agentStartToken)
chat.removeAttribute('session-id')
Managed-layout component resolvers update and remove props on the existing
custom element. They remount only when tagName changes, preserving the chat
input, scroll position, and element-owned lifecycle state across panel updates.
<wippy-chat-messages> and <wippy-chat-input>
The message list and the composer as separate elements, so you can lay them out yourself. Each takes a single session-id; with no explicit session-id they follow the shared active session set by a <wippy-session-selector>. Neither emits events.
<!-- Custom layout: messages above, composer below -->
<div style="display:flex; flex-direction:column; height:100%;">
<wippy-chat-messages session-id="019eb2ae-…"></wippy-chat-messages>
<wippy-chat-input session-id="019eb2ae-…"></wippy-chat-input>
</div>
<wippy-session-selector>
A session picker. It drives the shared active session that other elements follow.
| Attribute | Type | Default | Description |
|---|---|---|---|
active-session-id |
string | — | Highlight this session as active. |
Event:
| Event | detail |
When |
|---|---|---|
select |
{ sessionId: string } |
The user picks a session. The picked session becomes the shared active session. |
<wippy-session-selector></wippy-session-selector>
document.querySelector('wippy-session-selector')
.addEventListener('select', (e) => {
console.log('picked:', e.detail.sessionId)
})
Composition and shared session
Elements with no explicit session-id follow the <wippy-session-selector>'s pick via the manager's shared activeSessionId. So a selector plus a chat (or a selector plus a separate messages + input) on one page stay in sync — pick a session in the selector and the others update. Elements that do carry an explicit session-id (or start-token) are pinned and ignore the selector.
<!-- Selector + chat: the chat follows the picked session -->
<wippy-session-selector></wippy-session-selector>
<wippy-chat></wippy-chat>
<!-- Selector + split message list / composer, all following the selector -->
<wippy-session-selector></wippy-session-selector>
<wippy-chat-messages></wippy-chat-messages>
<wippy-chat-input></wippy-chat-input>
<!-- Pinned chat alongside a selector-driven one -->
<wippy-chat session-id="019eb2ae-…"></wippy-chat> <!-- ignores the selector -->
<wippy-chat></wippy-chat> <!-- follows the selector -->
Live steering and delivery state
Available with Web Host 1.0.59 and wippy/session 0.6.3. The built-in chat,
<wippy-chat>, and the separate input and message elements share the same
handlers. Enable steering on the session or agent; no frontend flag is needed.
See Sessions for the traits, policy tool, and
Stop lifecycle.
The composer follows the session's public interaction state when the server
provides it:
interaction?: {
can_send: boolean
revision: number
}
When can_send is true during generation, the composer shows Send beside
Stop. Stop still follows the session status. A newer revision replaces the
current interaction state, including an explicit false. Older revisions are
ignored. A malformed interaction update leaves the last valid state in place.
A full REST response with no interaction clears the cached value and restores
the legacy status-based behavior. A partial WebSocket update that omits it does
not clear the current value.
The steering-aware session command path uses the existing WebSocket
request_id response mechanism. The server assigns the message ID and returns
the existing received event after persistence. A valid receipt matching the
send request clears the draft and attachments. Other receipts update history
without clearing an unconfirmed draft. The acknowledgement and broadcast use
the same message merge path and server message ID.
Rejected, malformed, and uncertain responses keep the draft and attachments
and do not create a local pending message.
If delivery cannot be confirmed because the acknowledgement is lost, malformed, or times out, the composer keeps the draft and reports that the message may have been accepted. Refresh the session before sending it again. There is no automatic retry or client-side deduplication, so resending may create a duplicate.
Steering messages are displayed as pending until the session applies them to a
model prompt. The existing message topic carries a type: "update" patch with
the changed input metadata. Applied state always wins if events arrive out of
order. Pending messages outside the current history page are merged by their server message
ID. Reload and WebSocket reconnect refresh the selected session and history.
If that refresh fails, the current visible state is preserved.
Stop is confirmed by a session update carrying its request ID after the stop
state is committed. Rejected commands return an error event with the same
request ID. The built-in chat and the chat web components share these handlers.
Theming
Each element renders in a shadow root, so host page styles do not leak in or out. Two mechanisms apply theme:
- Inherited CSS variables. Theme custom properties (
--p-primary-*,--p-text-color, …) inherit across the shadow boundary from the host theme, so the chat follows the active palette and dark/light mode. Selector-based styles (PrimeVue, markdown, Tailwind) are bundled into achat-elements.csssheet and injected into the shadow root.PrimeVuePluginredirects the default body/null Portal target to a pinned overlay layer inside the owning shadow root. Do not setappendTo: 'self'routinely: that is an explicit inline-placement opt-in and can clip inside scrolling Dialog or Drawer content. Toasts are delegated to the host's native toast over the proxy rather than rendered in-shadow. - Per-instance overrides. Every element accepts two attributes:
| Attribute | Type | Effect |
|---|---|---|
custom-css |
string | Raw CSS appended last into the element's shadow root, so it wins by order. |
css-variables |
object (JSON) | Per-instance CSS variable overrides applied to :host. Keys may omit the leading --. |
Treat both attributes as trusted application configuration. Do not copy untrusted user input into raw CSS or variable values; CSS can alter or obscure the embedded interface and can initiate external resource requests.
<wippy-chat
session-id="019eb2ae-…"
custom-css=".message-item { max-width: 80%; }"
></wippy-chat>
Omitting css-variables is the normal facade-respecting path. Per-instance color overrides are for deliberate embedding isolation, not routine restyling.
For the full theming model — semantic variables, dark/light flipping, and how the host injects shadow-DOM CSS — see Theming: Web Components.
Cancelled tool calls
In Web Host 1.0.66, the built-in chat and the chat elements show a tool call that the Session cancelled as cancelled, with the Stop icon. They do not show it as an error. This applies when Stop, a failed turn, an agent handoff, or a command bus failure cancels the call, and also after a reload. Two classes mark the cancelled state:
| Class | Element |
|---|---|
chat-message--cancelled |
The content of a single tool row |
chat-tool-group__badge--cancelled |
A badge in a group of tool rows |
The default style is a muted left border. A theme that restyles
chat-message--success, chat-message--error, or
chat-tool-group__badge--success for tool rows may need rules for the new
classes as well.
The Host reads outcome: "cancelled" on the function_error event, and
status: "error" with outcome: "cancelled" on the stored function row.
The live state needs a Session that sends outcome on the event, which is
wippy/session 0.6.9 or newer. Session 0.6.8 sends status: "cancelled" on
the event instead, and the Host does not read it.
Runtime wiring
Inside a srcdoc iframe child, the elements require no additional setup. Auth and config come from the injected proxy runtime; REST and WebSocket use the config's environment URLs. When a chat tag mounts, the already-registered shell loads the internals on demand and connects with the child's existing session. Web Fragment and direct-host contexts have the availability limits described in How they load.
See Also
- Web Component (
view.component) — building your own custom element - @wippy-fe Packages — the host import map and injected element shells (
@wippy-fe/chat,@wippy-fe/loading) - Theming: Web Components — shadow-DOM CSS and semantic variables
- Proxy API —
host.startChat/host.openSessionand the rest of@wippy-fe/proxy - Proxy & Isolation — how the host injects scripts and config into children
UI policy in 1.0.62
Web Host 1.0.62 shares the following chat policy with child applications. hostConfig belongs to the Web Host wrapper and is never included in child payloads. A managed layout has no built-in chat by default. Applications that live entirely in a child use top-level policy or package overrides.