# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **RMon** is a real-time data monitor and plotter for distributed control systems accessed over SSH. The UI runs locally; data is acquired by a remote agent. See `usecase.md` for requirements and `docs/` for full specs. ## Language and Tech Stack **Rust throughout** — single language for agent, UI, and shared types. | Crate | Role | Key deps | |-------|------|----------| | `rmon-common` | Shared types, protocol message enums | `serde`, `bincode` | | `rmon-agent` | Remote data acquisition server | `tokio`, `tracing`, `anyhow`, `toml`, `serde` | | `rmon-ui` | Operator desktop application | `egui`, `eframe`, `egui_plot`, `tokio`, `tracing` | ## Build Commands ```sh # Build everything (debug) cargo build # Build the agent as a fully static binary (primary deployment target) cargo build --release --target x86_64-unknown-linux-musl -p rmon-agent # Build agent for ARM (e.g. remote Raspberry Pi) cargo build --release --target aarch64-unknown-linux-musl -p rmon-agent # Build the UI for the current platform cargo build --release -p rmon-ui # Run tests cargo test # Run tests for a single crate cargo test -p rmon-common cargo test -p rmon-agent # Run a single test cargo test -p rmon-agent sources::csv::tests::parse_timestamp # Lint cargo clippy --all-targets --all-features # Format cargo fmt ``` ## Architecture (short version) Two executables communicate via a binary TCP protocol over an SSH tunnel: - **`rmon-agent`**: runs on the remote machine, acquires data from heterogeneous sources (CSV, UDP, EPICS…), stores it, streams it to connected clients. Built as a fully static musl binary — single file, zero runtime deps, `scp` + run. - **`rmon-ui`**: runs on the operator's local desktop. Manages the SSH tunnel automatically (uses the system `ssh` binary + user's `~/.ssh/config`). Renders real-time plots with `egui`/`egui_plot`. - **`rmon-common`**: library crate shared by both. Contains all types (`SignalId`, `SignalInfo`, `Sample`, `StorageMode`) and the `ClientMessage`/`AgentMessage` protocol enums serialized with `bincode`. Full architecture: `docs/architecture.md` Wire protocol: `docs/protocol.md` Data model: `docs/data-model.md` Agent config and source trait: `docs/agent-spec.md` UI layout and state machine: `docs/ui-spec.md` ## Key Design Decisions - **Protocol framing**: `[u32 LE length][bincode payload]` — no gRPC/HTTP overhead. Both sides are Rust so `bincode` + shared enum types is simplest. - **Agent portability**: `x86_64-unknown-linux-musl` gives a fully static binary requiring only Linux kernel ≥ 3.2. The agent never has a GUI, so no X11/GL needed. - **Remote access UX**: The UI deploys the agent binary via SSH itself (like VSCode Remote). Users only need to enter their SSH host alias; existing `~/.ssh/config` (including `ProxyJump`) handles multi-hop routing. - **UI rendering**: `egui` immediate-mode with `glow` (OpenGL 2.1) backend. For signals with more points than screen pixels, min-max downsampling runs on the CPU before drawing. - **Timestamps**: all source timestamps are normalized to `i64` nanoseconds since Unix epoch inside the source adapter. The rest of the codebase only sees `i64`. - **Concurrency (agent)**: one tokio task per data source → `broadcast` channel → dispatcher fans out to storage writers and per-client session tasks. - **UI/async boundary**: tokio runtime in a background thread; egui on main thread. `std::sync::mpsc` channels carry `UiEvent` (agent→UI) and `Command` (UI→agent). ## Adding a New Data Source 1. Create `rmon-agent/src/sources/{name}.rs` 2. Implement the `DataSource` trait (`name()`, `signals()`, `async fn run(self, tx)`) 3. Add a new variant to `SourceConfig` in `config.rs` and construct it in `sources/mod.rs` 4. Add config example to `docs/agent-spec.md` ## Protocol Changes All protocol types live in `rmon-common/src/protocol.rs`. Since `bincode` is not self-describing, any change to `ClientMessage` or `AgentMessage` requires a protocol version bump in the `Handshake` message and both sides must be rebuilt together.