Architecture
Chat UI is intentionally layered so transport/session logic stays outside presentation components.
Layer Model
1. Presentation Layer
src/routes/+page.sveltesrc/lib/components/*
Responsibility: render state and emit user intent.
2. Connection Orchestration
src/lib/session/connection-controller.svelte.ts
Responsibility:
- create/replace
NullclawClient - pairing/connect lifecycle
- auth restore/logout handling
- feed protocol events into session store
3. Session Store
src/lib/stores/session.svelte.ts
Responsibility:
- chat timeline
- tool call/result timeline
- approval requests
- streaming/error cleanup
4. Transport + Protocol
src/lib/protocol/client.svelte.tssrc/lib/protocol/types.tssrc/lib/protocol/e2e.ts
Responsibility:
- envelope validation
- websocket state machine
- reconnect backoff
- E2E encrypt/decrypt helpers
5. Persistence + Preferences
src/lib/session/auth-storage.tssrc/lib/theme.tssrc/lib/ui/preferences.ts
Responsibility: auth TTL storage + theme/effects storage.
Client States
NullclawClient states:
disconnectedconnectingpairingpairedchatting
Core Invariants
- components do not manage websocket lifecycle directly
- unauthorized session invalidates persisted auth
- timeline rendering is store-driven from protocol envelopes