# Architecture overview
This document gives a slightly deeper view of how the project is structured internally. It is meant to be read alongside the generated Rustdoc (`cargo doc --open`).
## High-level layers
The project is a **single Cargo package** (not a workspace). The crate exposes a library root (`src/lib.rs`, `romm_cli`) alongside the `romm-cli` binary so integration tests and helper binaries can reuse the same modules. A second binary, `romm-tui`, only launches the TUI.
A future split into `romm-api` / `romm-cli` / `romm-tui` crates is **not planned**; see [workspace-split ADR](plans/2026-06-06-workspace-split-adr.md). Library consumers should use `romm-cli` with `--no-default-features` to avoid TUI dependencies until a split is triggered.
Configuration is layered per field: built-in defaults → `config.json` → environment variables → OS keyring (secret sentinels) → command-specific CLI runtime overrides. See the README [*Configuration precedence*](../README.md#configuration-precedence) section and [`src/config.rs`](../src/config.rs) module documentation for the full model. Secrets (like passwords and tokens) may be stored in the OS keyring via `keyring::Entry` with a `<stored-in-keyring>` sentinel in JSON only after a successful read-back verification. Note that `Commands::Init` is handled in `main.rs` *before* `load_config` so that `init` can run even if no configuration exists yet.
From bottom to top:
- **Types & endpoints**
- `types.rs` – data models used throughout the app.
- `endpoints/*` – implementations of the `Endpoint` trait describing
the HTTP method, path, query params, and optional body for each
ROMM API endpoint. Endpoint modules are organized by API area
(`platforms`, `roms`, `collections`, `client_tokens`, `device`, `saves`, `sync`, `system`, `tasks`).
- **Core services** (`src/core/`, `src/client.rs`, `src/config.rs`)
- `Config` / `AuthConfig` – decide how to talk to ROMM (base URL and
authentication mode).
- `RommClient` – wraps `reqwest::Client` and uses `Endpoint` values to
perform typed HTTP calls.
- `RomCache` – small disk-backed cache for ROM lists, keyed by
platform/collection.
- `DownloadManager` – orchestrates background downloads and exposes a
shared list of `DownloadJob`s.
- **Frontends** (`src/frontend/`)
- **CLI** (`src/commands/*`) – one-shot commands for platforms/ROMs/API/auth. The `frontend::cli` module routes parsed arguments to these handlers.
- **TUI** (`src/tui/*`) – an event loop and a set of screens that
present and manipulate the underlying data.
### TUI event loop
The TUI follows an **Event → Action → update → render** pipeline (see [Gap 5 in rust-guidelines.md](rust-guidelines.md#gap-5-tui-event--action-separation)):
```text
poll_frame_events() # drain_background_events + crossterm input
→ map_event() # AppEvent → Action
→ App::update() # single state-mutation entry point
→ render() # ratatui draw (screens are render-only)
```
- [`src/tui/app/event.rs`](../src/tui/app/event.rs) – `AppEvent`, `Action`, global key mapping
- [`src/tui/app/update.rs`](../src/tui/app/update.rs) – applies actions (navigation, spawns, background completions)
- [`src/tui/app/run.rs`](../src/tui/app/run.rs) – thin loop using [`src/tui/runtime.rs`](../src/tui/runtime.rs) (`TuiSession`)
- [`src/tui/app/handlers/screen_keys.rs`](../src/tui/app/handlers/screen_keys.rs) – per-screen key → action dispatch
- First-run setup wizard reuses `TuiSession` and [`setup_wizard/event.rs`](../src/tui/screens/setup_wizard/event.rs)
The CLI layer itself is split into:
- `commands::mod` – top-level `Cli` and `Commands` enum plus `OutputFormat`.
- `commands::platforms` / `commands::roms` / `commands::api` / `commands::auth` / `commands::download` / `commands::scan` / `commands::sync` / `commands::cache` / `commands::init` / `commands::update` – small modules that parse arguments, call into services, and print results. Library scan HTTP for both `scan` and upload-triggered scans lives in `commands::library_scan`.
- `commands::print` – helpers for tabular text output.
- `core::resolve` – platform/collection name→ID helpers shared by CLI commands.
- `core::roms` – paginated ROM list fetching shared by TUI prefetch paths.
There are no TUI/CLI dependencies inside the core services, which makes it straightforward to add more frontends later.
## Data flow
Roughly:
```text
Config + env + OS Keyring
↓
RommClient (HTTP + auth)
↓
Endpoint implementations
↓
typed responses (types.rs)
```
The TUI and CLI both operate on the same `RommClient` and model types.
## Why an enum-based state machine?
The TUI uses:
- `AppScreen` – an enum with variants for each high-level screen (`MainMenu`, `LibraryBrowse`, `Search`, `Settings`, `GameDetail`, `Download`, `SetupWizard`).
- `App` – a struct that owns shared services (`RommClient`, `RomCache`, `DownloadManager`) and the current `AppScreen`. It also holds shared state like `save_sync_compat` (from OpenAPI at startup), `server_version`, `startup_splash`, and `deferred_load_roms`.
Each key press is dispatched to a method like `handle_main_menu` or `handle_library_browse`, which matches on `self.screen`, mutates it, and possibly transitions to another variant.
This pattern works well in Rust because:
- The compiler forces you to handle all variants in `match` statements.
- Ownership is explicit (you often move a screen out of the enum, mutate it, then put it back).
You could also model screens as trait objects, but the enum-based approach keeps everything static and easy to follow for learners.