This document explains how the current codebase is wired together and where to start when changing behavior. It is written for contributors who need a quick, accurate map of the runtime instead of a marketing overview.
OpenTokenMonitor is a Tauri desktop app with:
- a React + TypeScript frontend in
src/ - a Rust backend in
src-tauri/src/ - a SQLite persistence layer managed by the Rust backend
- provider adapters that combine local CLI artifacts with live authenticated fetches
The important design choice is that the frontend does not talk to provider APIs directly. The React app talks to the Rust backend through Tauri commands, and the Rust backend owns provider fetch logic, persistence, background refreshes, and filesystem watching.
- Tauri starts the Rust backend from
src-tauri/src/main.rs. src-tauri/src/lib.rsbuilds the Tauri app, initializes plugins, opens the SQLite-backedUsageStore, registers providers, configures the tray, starts file watchers, and starts the poll scheduler.- React mounts
src/App.tsx. App.tsxactivates:useGlassThemeto apply the current themeuseUsageDatato bootstrap usage snapshots and subscribe to backend eventsuseProviderStatusto poll provider availability
- Zustand stores in
src/stores/become the shared source of truth for the UI.
The normal refresh path looks like this:
- React calls a store action such as
refreshAll()insrc/stores/usageStore.ts. - The store calls a Tauri command through
invoke(...). - Rust receives the command in
src-tauri/src/lib.rs. - The command delegates to
src-tauri/src/usage/aggregator.rs. - The aggregator asks a provider implementation for fresh usage.
- The backend writes snapshots and cost history into SQLite.
- Rust emits a
usage-updatedevent. useUsageDatalistens for that event and upserts the updated snapshot into the frontend store.
Two backend systems keep the UI current even when the user does not press refresh:
src-tauri/src/watchers/poll_scheduler.rsPeriodically triggers refreshes based on the configured cadence.- filesystem watchers under
src-tauri/src/watchers/React to changes in local CLI artifacts and re-run provider refreshes.
App.tsx is the frontend orchestrator. It is responsible for:
- deciding whether the app is in widget mode or full dashboard mode
- managing current page selection
- kicking off initial fetches
- coordinating refreshes across providers
- syncing desktop-only settings such as launch-on-startup
- resizing the Tauri window when widget mode changes
If you need to understand "what happens when the app opens?", start here.
This is the main frontend bridge to the backend. It stores:
- current snapshots
- trends
- model breakdowns
- recent activity
- provider status results
- generated alerts and reports
Every action in this store is intentionally thin:
- call a backend command
- normalize the returned shape into per-provider maps
- update Zustand state
If backend data is in the app but not on screen, this is usually the first place to inspect.
This hook handles usage bootstrap and live synchronization:
- tries
refreshAll()first so the app prefers fresh backend data - falls back to cached snapshots when refresh fails
- fetches recent activity for every provider
- listens for the backend
usage-updatedevent and merges incoming snapshots
This hook polls provider availability independently from usage snapshots. That separation lets the UI distinguish:
- "provider is reachable but usage is still loading"
- "provider is only partially available"
- "provider is unavailable"
The main UI surfaces are grouped by responsibility:
components/layout/Sidebar, widget mode, widget activity surfacecomponents/providers/Overview cards and full provider detail screenscomponents/settings/Settings and About panelscomponents/meters/Circular/widget gauges, reset countdowns, and usage meterscomponents/states/Empty, loading, error, and diagnostics states
This file is the backend composition root. It contains:
- the shared
AppState - Tauri command handlers
- startup/shutdown behavior
- tray setup
- autostart support
- event emission after refreshes
- scheduler and watcher wiring
This is the best place to start when a frontend invoke(...) call is failing.
Provider implementations live here. Each provider conforms to the UsageProvider
trait in src-tauri/src/providers/mod.rs:
fetch_usagefetch_cost_historycheck_status
registry.rs is the one place where providers are registered. Adding a new
provider always means:
- implement the trait
- register it in
ProviderRegistry::new() - update any frontend provider enums or metadata maps
The aggregator is deliberately simple. It does not own provider-specific logic. Its job is to:
- ask a provider for fresh usage
- persist the snapshot
- persist optional cost history
- aggregate provider errors when running
refresh_all
This module owns SQLite persistence. It is the durable source for:
- latest snapshots
- cost history
- model breakdown queries
- usage trend queries
When the UI asks for cached historical data, the answer comes from here.
This module scans local recent CLI activity and turns that into the prompt history shown in the widget and provider detail pages.
These modules keep local-file-driven providers reactive:
file_watcher.rsWatches the relevant directories and triggers provider refreshespoll_scheduler.rsOwns the repeatable timer used for cadence-based refreshes
The backend is authoritative for persisted usage data.
- Frontend state is a projection for rendering and interaction.
- Backend SQLite is the durable store.
- Provider modules are the only layer that should know how Claude, Codex, or Gemini are fetched.
This separation matters because it keeps provider quirks out of the React tree.
- Update the Rust model in
src-tauri/src/usage/models.rs - Update provider fetchers to fill the field
- Update persistence queries if the field must be stored
- Update the TypeScript mirror in
src/types.ts - Render the field in the relevant React component
- Start in
src-tauri/src/usage/aggregator.rs - Check provider logic in
src-tauri/src/providers/<provider>/ - Check event emission and scheduler wiring in
src-tauri/src/lib.rs - Confirm the frontend hook/store path in
useUsageData.tsandusageStore.ts
src/components/layout/WidgetMode.tsxsrc/components/layout/WidgetActivityView.tsxsrc/components/meters/WidgetGauge.tsxsrc/styles/sidebar.csssrc/App.tsxif the window size must change
If you are new to the repo, read in this order:
README.mdARCHITECTURE.mdsrc/App.tsxsrc/stores/usageStore.tssrc/hooks/useUsageData.tssrc-tauri/src/lib.rssrc-tauri/src/providers/mod.rssrc-tauri/src/usage/aggregator.rs
That sequence gives the fastest path from "what is this app?" to "where do I make the change?"