romm-cli 0.40.0

Rust-based CLI and TUI for the ROMM API
Documentation
# 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.