# Daemon Web UI Adapter
## Goal
Web chat and web terminal clients should consume `qwen serve` through the
daemon HTTP/SSE APIs and render a client-side transcript. Native local TUI,
channel, and IDE integrations keep their existing default paths for now.
## Shared UI Contract
Use the TypeScript SDK daemon UI exports as the common boundary:
```ts
import {
DaemonClient,
DaemonSessionClient,
createDaemonTranscriptStore,
normalizeDaemonEvent,
} from '@qwen-code/sdk/daemon';
```
The split is:
- `DaemonClient` handles daemon HTTP routes.
- `DaemonSessionClient` owns session creation/attachment and SSE replay.
- `normalizeDaemonEvent()` converts daemon wire events into UI events.
- `createDaemonTranscriptStore()` reduces UI events into transcript blocks.
React clients can use the optional `@qwen-code/webui` binding:
```tsx
import {
DaemonSessionProvider,
useDaemonActions,
useDaemonConnection,
useDaemonPendingPermissions,
useDaemonTranscriptBlocks,
} from '@qwen-code/webui';
```
Minimal React shape:
```tsx
function App() {
return (
);
}
function Transcript() {
const blocks = useDaemonTranscriptBlocks();
return blocks.map((block) => );
}
```
The provider creates or attaches a daemon session, subscribes to SSE, keeps the
last event id on `DaemonSessionClient`, and reconnects the stream by default.
Callers can disable that with `autoReconnect={false}` for tests or custom
connection management.
## Browser Deployment Shapes
### Same-Origin Local POC
A daemon-served page can call the daemon directly because the page and API share
one origin. This is the preferred early POC shape for local web chat and web
terminal validation.
### Remote Web Chat / Web Terminal
A production remote web app should normally talk to a backend-for-frontend. The
BFF owns daemon URL, token, workspace routing, and session metadata, then
forwards browser-safe app events to the browser. This keeps bearer tokens out of
browser storage and lets the deployment decide which daemon/workspace a user is
allowed to reach.
### Local Browser Against Local Daemon
A separate local dev server is cross-origin from `qwen serve`; it must either
proxy daemon routes through the same origin or be served by the daemon. The
daemon intentionally rejects arbitrary browser `Origin` requests.
## Rendering Responsibilities
The shared transcript model is semantic, not visual. UI clients decide how to
render:
- user and assistant message blocks
- collapsed thought blocks
- tool status cards
- shell output blocks
- permission request controls
- status/error/debug blocks
The web terminal is a browser-native semantic renderer. It should look and feel
terminal-like with monospace layout, scrollback, prompt input, shortcuts, and
streaming blocks, but it is not a raw PTY proxy and does not require server-side
Ink rendering.
## Merge Safety
- The native `qwen` TUI remains direct and unchanged.
- `--acp`, channel, and IDE paths remain unchanged by default.
- The SDK UI core is additive.
- The WebUI React binding is optional and only runs in clients that import it.
- Removed daemon TUI spike code should not be treated as a product migration.
## Follow-Ups
- Add a daemon-served local `/web` POC or equivalent same-origin web app.
- Build first-class chat and terminal renderers on top of transcript blocks.
- Add richer typed events only where existing daemon events are too low-level
for stable browser UI behavior.
- Consider a dedicated `@qwen-code/daemon-ui-core` package if non-SDK consumers
need the UI core as an independent dependency.