Frontend Clients
Diminuendo ships with two frontend clients — a web application and a desktop application — that share a common codebase through an npm workspaces monorepo. The architectural goal is clear: write UI components, state management, and business logic once, and run it identically across platforms by swapping only the transport layer.Monorepo Structure
Theclients/ directory is an npm workspaces monorepo containing three packages:
@igentai/dim-shared
Types, gateway adapter interface, Zustand stores, React hooks, and shared UI components. This is the foundation that both clients build upon.
@igentai/dim-web
Vite + React web application. Connects to the gateway directly via WebSocket using the TypeScript SDK and the
WebGatewayAdapter.@igentai/dim-desktop
Tauri v2 + React desktop application. Connects via IPC to a Rust backend that holds the WebSocket connection using the Rust SDK and the
TauriGatewayAdapter.Shared Package
The@igentai/dim-shared package contains everything that is platform-agnostic:
Types
- Protocol types (
types/protocol.ts) — TypeScript interfaces mirroring the gateway’s wire protocol:SessionMeta,StateSnapshotEvent,ServerEventunion, and all event subtypes - Domain types (
types/domain.ts) — Application-level types:ChatMessage,ToolCallBlock,ThinkingBlock, and the rendered message structure - Store types (
types/store.ts) — Zustand store shape interfaces
Gateway Adapter
TheGatewayAdapter interface (gateway/adapter.ts) is the central abstraction. It defines connect(), disconnect(), an events stream, and all 21 protocol methods using Effect types. See Gateway Adapter Pattern for a detailed treatment.
Zustand Stores
Four Zustand stores manage application state:| Store | File | Responsibility |
|---|---|---|
| Connection | stores/connection.ts | Gateway connection status, authenticated identity, adapter reference |
| Sessions | stores/sessions.ts | Session list, active session selection, session metadata cache |
| Chat | stores/chat.ts | Message history, streaming text accumulation, tool call tracking, thinking blocks |
| Preferences | stores/preferences.ts | Theme, sidebar collapsed state, user preferences |
React Hooks
useGatewayConnection()— manages the adapter lifecycle, connects/disconnects, feeds events into storesuseSession()— provides the active session, join/leave logic, and session-scoped event handlinguseChat()— provides chat messages, streaming state, and turn interaction methods (send message, stop turn, answer question)useAutoScroll()— handles automatic scroll-to-bottom behavior during streaming responsesuseTheme()— manages light/dark theme detection and persistence
UI Components
Shared shadcn/ui components styled with Tailwind CSS v4, including the chat container, message list, assistant message renderer (usingreact-markdown + remark-gfm + shiki for syntax highlighting), tool result blocks, and the session sidebar.
Key Dependencies
| Package | Version | Purpose |
|---|---|---|
| React | 19 | UI framework |
| Effect | ^3.12 | Typed effects for gateway adapter |
| Zustand | 5 | State management |
| Tailwind CSS | v4 | Styling |
| shadcn/ui | latest | Component library |
| lucide-react | 0.470 | Icon set |
| react-markdown | 10 | Markdown rendering |
| remark-gfm | 4 | GitHub-flavored markdown support |
| shiki | 3 | Syntax highlighting |
| Vite | 6 | Build tool (web client) |
| @tauri-apps/cli | ^2.2 | Desktop build tool |
| @tauri-apps/api | ^2.2 | Tauri IPC API |
The Gateway Adapter Pattern
TheGatewayAdapter interface is the key architectural abstraction that makes code sharing possible. Both clients use identical React components and Zustand stores. The only difference is which adapter implementation is injected at initialization time:
- Web client:
WebGatewayAdapterwraps the TypeScript SDK’sDiminuendoClient, translating Promise-based methods to Effect and delivering events as an EffectStream. - Desktop client:
TauriGatewayAdapterwraps Tauri’sinvoke()(for commands) andlisten()(for events), bridging from the Rust SDK running in the Tauri backend.
State Management Architecture
Events flow from the gateway through the adapter and into Zustand stores in a unidirectional pattern:1
Gateway Event
The gateway sends a server event over WebSocket (or Tauri IPC).
2
Adapter Stream
The
GatewayAdapter.events stream emits the parsed event.3
Event Router
The
useGatewayConnection hook’s event subscription routes each event to the appropriate store based on its type field.4
Store Update
The targeted Zustand store updates its state (e.g., the chat store appends a
text_delta to the current message’s streaming buffer).5
React Re-render
Components subscribed to that store slice re-render with the new state. Zustand’s selector-based subscriptions ensure only affected components re-render.
Development
ws://localhost:8080/ws).