WinSTT logoWinSTT
Architecture

Frontend Architecture

Tauri WebView + Vite multi-page React with Feature-Sliced Design

The WinSTT frontend is a Vite multi-page React 19 renderer running inside Tauri's WebView (WebView2 on Windows). Each Tauri window loads its own HTML entry; there is no router. The renderer holds no model or socket code — it reaches the Rust backend through Tauri commands and events.

8
Tauri windows
6
Feature-Sliced layers
~200
typed Tauri commands
20
interface locales

One generated, typed boundary

The renderer reaches Rust through the tauri-specta-generated client in src/bindings.ts. Every call site calls commands.* directly, so a Rust signature change breaks the TypeScript build.

Process Architecture

Rust backend

All of the application's work lives in Rust (src-tauri/): audio capture, VAD, STT/TTS inference, the LLM and cloud integrations, global hotkey, system tray, window management, and settings/history persistence. See the backend architecture.

The native boundary

Renderer → Rust calls go through the generated commands.* client in src/bindings.ts. Rust → renderer pushes go the other way as events: their names live once as Rust consts and are mirrored for the renderer in src/shared/api/native-events.ts, with src/shared/api/native-boundary.ts owning the Tauri listen/invoke boundary itself. src/shared/api/ipc-client.ts is a re-export barrel over those modules.

Renderer (multi-window Vite)

Each Tauri window loads its own HTML entry — index.html for the main pill, windows/<page>.html for the rest — and mounts one React tree from src/entries/<page>.tsx:

One HTML entry and one React tree per Tauri window — there is no shared router.
WindowHTML entryReact entry
Main pillindex.htmlentries/main.tsx
Settingswindows/settings.htmlentries/settings.tsx
Model pickerwindows/model-picker.htmlentries/model-picker.tsx
Historywindows/history.htmlentries/history.tsx
Overlaywindows/overlay.htmlentries/overlay.tsx
Tray menuwindows/tray-menu.htmlentries/tray-menu.tsx
Onboardingwindows/onboarding.htmlentries/onboarding.tsx

Feature-Sliced Design Layers

src/
  app/        Bootstrap, providers (IpcProvider, intl, stores), global styles
  views/      One slice per window (main, settings, overlay, history, …)
  widgets/    Composite blocks combining features and entities
  features/   Reusable user interactions (push-to-talk, model-download, …)
  entities/   Business domain objects (model-catalog, transcription, setting, …)
  shared/     Foundation: api (ipc-client, adapter, bindings), ui, lib, config
  i18n/       Locale messages (use-intl)
  entries/    One .tsx per window (createRoot + render <View />)

Import Contract

One direction only

Each layer may import only from the layers below it — never sideways, never upward. shared sits at the bottom and depends on nothing; app sits at the top and may reach any layer.

LayerMay import from
appall layers
viewswidgets, features, entities, shared
widgetsfeatures, entities, shared
featuresentities, shared
entitiesshared
sharednothing

State Management

ToolPurpose
ZustandRenderer client state (settings, model catalog, transcription)
Tauri commands + eventsSource-of-truth data layer (no HTTP, no TanStack Query, no Redux)

Settings are hydrated from winstt_get_settings; the update-settings feature listens for the settings:changed event and fans out hot-swap commands (model reload, VAD slider, autostart) so most changes apply without a restart.

Technology Stack

ConcernChoice
ShellTauri 2 (WebView2 on Windows)
Package managerBun
Build toolVite 8 (renderer) + Cargo/Tauri (backend)
LanguageReact 19 + TypeScript (React Compiler in prod)
Linter / type checkBiome (via Ultracite) + tsc
Test runnerbun test
UI components@base-ui/react (Base UI by MUI)
StylingTailwind CSS v4 + clsx + tailwind-merge + class-variance-authority
Icons@hugeicons/react + @hugeicons/core-free-icons
Animationmotion (Framer Motion v12)
Formsnative <form> + useState + Zod safeParse (no form library)
i18nuse-intl (20 locales)

LLM runs in the backend

Talking to Ollama/OpenRouter happens in the backend (LlmManager, ollama-rs + reqwest), so the renderer ships no AI SDK — it just invokes process_text / process_transform.

On this page