Tauri-aware app crate for HardwareVisualizer.
This crate is the upper half of the Core / App split introduced by
#1402. It depends on hardviz-core via a workspace
path dependency and is the only crate in the workspace that links against
tauri. Everything that requires an AppHandle, a window, or a Tauri plugin
lives here; everything else belongs in hardviz-core.
- Expose Tauri commands as the UI ↔ backend boundary; generate the matching
TypeScript bindings via
tauri-specta. - Translate Core
MetricsSnapshotevents into Tauri events the frontend consumes (HardwareMonitorUpdate). This is the only place that callswindow.emit(...)for sensor updates. - Drive App startup: resolve OS-specific paths, hand the SQLite path to Core, surface the Core preflight result to the user (recovery dialog, restart), bring up Tauri plugins.
- Own UI-only settings (theme, language, line graph styling, burn-in shift, temperature unit, background image, …) that have no effect on Core.
- Own App-side services for background images, language, settings, system lifecycle, UI-local state, and App DTO conversion.
- Hold worker handles (
WorkersState) so the App can terminate Core controllers and adapters cleanly on quit.
src-tauri/src/
├── lib.rs ← run() — Tauri builder, command registration, setup hook
├── main.rs ← thin binary entry point
├── commands/ ← Tauri command handlers (UI boundary)
│ ├── background_image.rs
│ ├── hardware.rs
│ ├── settings.rs
│ ├── system.rs
│ ├── ui.rs
│ └── updater.rs
├── services/ ← App-side business logic
│ ├── background_image_service.rs
│ ├── gpu_service.rs / memory_service.rs / motherboard_service.rs / network_service.rs / …
│ ├── hardware_service.rs
│ ├── language_service.rs
│ ├── settings_service.rs
│ ├── system_service.rs
│ └── ui_service.rs
├── adapters/ ← Core EventBus → App outputs
│ ├── window.rs subscribes to MetricsSnapshot, emits HardwareMonitorUpdate
│ └── tray.rs subscribes to MetricsSnapshot, updates tray widget output
├── app/ ← App lifecycle helpers
│ └── startup.rs DB preflight error dialog + reset-and-restart flow
├── lifecycle.rs ← close-to-tray, second instance, and run-event policy
├── workers/ ← WorkersState — holds Core controller / adapter handles
├── tray/ ← tray widget windows, surface helpers, and UI policy
├── infrastructure/ ← App-only DB code
│ └── database/migration.rs SQL migration definitions (run by Core's migrator)
├── models/ ← App-side DTOs (HardwareMonitorUpdate, settings, storage health, …)
├── enums/ ← App-side enums (TemperatureUnit, hardware, settings, …)
├── utils/ ← App-side helpers (file paths, color, Tauri-aware logger)
└── _tests/ ← Unit and command-level tests
These rules are inherited from #1402 and apply across the App crate:
hardviz-coreis the source of truth for sensor state. Commands read through Core APIs (HistoryStore,MetricsSnapshot) — never through aMutexreached out of Core internals.- All
MetricsSnapshot → window.emit(...)translation lives underadapters/. The collector loop in Core never touchesAppHandle. - App lifecycle is App-owned. Window creation, plugin wiring, shutdown,
and any process-restart logic stay under
src-tauri/. Core lifecycle is driven by App-side controllers (SystemMonitorController,ArchiveController,StorageHealthController,WindowAdapter,TrayAdapter) held inWorkersState. - UI-only settings stay App-side. Theme, language, line graph styling,
burn-in shift,
temperatureUnit, and similar fields are owned here. Presentation conversions (e.g. °C → °F) happen in App, not in Core. - No business logic in
commands/. Commands validate input, format output, and delegate to a service or to the Core API.
src-tauri/src/services/ contains both thin Core wrappers and App-owned
services:
gpu_service,memory_service,motherboard_service, andnetwork_servicewrap Core platform APIs and convert results into App DTOs or App error types.hardware_serviceaggregates Core history, Core platform data, and provider data into App-facing system information.settings_service,background_image_service,language_service,system_service, andui_serviceown App-side behavior, Tauri-aware state, or UI-local persistence.
Within the App crate, the layering follows the existing
docs/architecture/backend.md:
Commands → Services → (Core API) ─ App-owned
↘ (Core: Platform → Infrastructure → OS APIs)
The platform/ and infrastructure/providers/ directories that the
architecture document references now live in core/src/; the
App crate retains only the App-specific infrastructure/database/
(the ordered schema migration definitions, executed at startup by Core's
migrator in hardviz_core::infrastructure::database::migrate).
- TypeScript bindings are generated by
tauri-spectafrom thecollect_commands![ ... ]macro inlib.rs. Output:src/rspc/bindings.ts(do not edit by hand — regenerate vianpm run tauri dev). - New backend commands must be added to
collect_commands![...]insrc-tauri/src/lib.rsto be exposed to the frontend. - Backend errors are surfaced to the frontend via
error_event; the frontend renders them throughuseErrorModalListener(src/hooks/useTauriEventListener.ts).
From the repository root:
# Desktop dev mode (regenerates TypeScript bindings)
# Uses the HardwareVisualizerDev app identity and separate app data.
npm run tauri dev
# Production build
npm run tauri build
# Cargo aliases (CI parity)
cargo tauri-fmt
cargo tauri-lint
cargo tauri-testThe cargo tauri-* aliases are defined in .cargo/config.toml at the
workspace root and operate on the full workspace, including hardviz-core.
- Epic: #1402 — split backend into Tauri-independent Core and thin App adapters
- Backend architecture:
docs/architecture/backend.md - Core crate:
core/README.md - Top-level user-facing README:
README.md