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

The clients/ directory is an npm workspaces monorepo containing three packages:
clients/
  shared/          @igentai/dim-shared
  web/             @igentai/dim-web
  desktop/         @igentai/dim-desktop
  package.json     Workspace root
  tsconfig.base.json

@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, ServerEvent union, 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

The GatewayAdapter 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:
StoreFileResponsibility
Connectionstores/connection.tsGateway connection status, authenticated identity, adapter reference
Sessionsstores/sessions.tsSession list, active session selection, session metadata cache
Chatstores/chat.tsMessage history, streaming text accumulation, tool call tracking, thinking blocks
Preferencesstores/preferences.tsTheme, sidebar collapsed state, user preferences

React Hooks

  • useGatewayConnection() — manages the adapter lifecycle, connects/disconnects, feeds events into stores
  • useSession() — provides the active session, join/leave logic, and session-scoped event handling
  • useChat() — provides chat messages, streaming state, and turn interaction methods (send message, stop turn, answer question)
  • useAutoScroll() — handles automatic scroll-to-bottom behavior during streaming responses
  • useTheme() — 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 (using react-markdown + remark-gfm + shiki for syntax highlighting), tool result blocks, and the session sidebar.

Key Dependencies

PackageVersionPurpose
React19UI framework
Effect^3.12Typed effects for gateway adapter
Zustand5State management
Tailwind CSSv4Styling
shadcn/uilatestComponent library
lucide-react0.470Icon set
react-markdown10Markdown rendering
remark-gfm4GitHub-flavored markdown support
shiki3Syntax highlighting
Vite6Build tool (web client)
@tauri-apps/cli^2.2Desktop build tool
@tauri-apps/api^2.2Tauri IPC API

The Gateway Adapter Pattern

The GatewayAdapter 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: WebGatewayAdapter wraps the TypeScript SDK’s DiminuendoClient, translating Promise-based methods to Effect and delivering events as an Effect Stream.
  • Desktop client: TauriGatewayAdapter wraps Tauri’s invoke() (for commands) and listen() (for events), bridging from the Rust SDK running in the Tauri backend.
Web:      React Component → Hook → Store → WebGatewayAdapter → DiminuendoClient → WebSocket
Desktop:  React Component → Hook → Store → TauriGatewayAdapter → invoke() → Rust → WebSocket
For the complete interface definition and adapter implementations, see Gateway Adapter Pattern.

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

# Install dependencies (from clients/ root)
npm install

# Start the web client
npm run dev:web          # Vite dev server on localhost:5173

# Start the desktop client
npm run dev:desktop      # Tauri dev window with hot reload
Both dev modes expect a running Diminuendo gateway instance (default: ws://localhost:8080/ws).