diff --git a/CLAUDE.md b/CLAUDE.md
index 8b8d1bf..ce30d43 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -71,14 +71,14 @@ The project is split into a **server** (connects to the radio hardware) and a **
### Data flow
-```
-Radio hardware
- ↕ serial/TCP
-trx-server (rig_task.rs)
- ↕ trx-protocol JSON-TCP (port 4530)
-trx-client (remote_client.rs)
- ↕ internal channels
-Frontends: HTTP (8080), rigctl (4532), http-json (ephemeral)
+```mermaid
+graph TD
+ Radio["Radio Hardware"] <-->|serial / TCP| Server["trx-server (rig_task.rs)"]
+ Server <-->|"JSON-TCP :4530"| Client["trx-client (remote_client.rs)"]
+ Server -->|"Opus-TCP :4531"| Client
+ Client <-->|internal channels| F1["HTTP Frontend :8080"]
+ Client <-->|internal channels| F2["rigctl Frontend :4532"]
+ Client <-->|internal channels| F3["JSON-TCP Frontend"]
```
### trx-core controller
@@ -94,6 +94,10 @@ The rig controller (`src/trx-core/src/rig/controller/`) is the central state man
Signal decoders run as background tasks in `trx-server`, consuming decoded audio. `trx-ftx` provides the FT8/FT4/FT2 decoder in pure Rust. Decoded frames can be forwarded to PSKReporter and APRS-IS (IGate) uplinks, or logged via `trx-decode-log`.
+## Diagrams
+
+Always use [Mermaid](https://mermaid.js.org/) for diagrams in Markdown files. Never use ASCII art, box-drawing characters, or plain-text diagrams. GitHub renders Mermaid natively in ```mermaid fenced code blocks.
+
## Commit Format
```
diff --git a/README.md b/README.md
index f35170a..3fa65db 100644
--- a/README.md
+++ b/README.md
@@ -82,14 +82,14 @@ Build without SDR support: `cargo build --release --no-default-features`
## How It Works
-```
-Radio / SDR hardware
- | serial or USB
-trx-server rig control, DSP, decoders, audio capture
- | JSON-TCP (4530) + Opus-TCP (4531)
-trx-client remote connection, audio relay
- |
-Frontends Web UI (8080), rigctl (4532), JSON-TCP
+```mermaid
+graph TD
+ Radio["Radio / SDR Hardware"] <-->|"serial or USB"| Server["trx-server
rig control, DSP, decoders, audio capture"]
+ Server <-->|"JSON-TCP :4530"| Client["trx-client
remote connection, audio relay"]
+ Server -->|"Opus-TCP :4531"| Client
+ Client <-->|internal channels| F1["Web UI :8080"]
+ Client <-->|internal channels| F2["rigctl :4532"]
+ Client <-->|internal channels| F3["JSON-TCP"]
```
`trx-server` owns hardware access and runs the DSP pipeline.
diff --git a/docs/Architecture.md b/docs/Architecture.md
index da3dedb..82d7f0b 100644
--- a/docs/Architecture.md
+++ b/docs/Architecture.md
@@ -58,36 +58,28 @@ Target users are amateur radio operators who want networked, automated, or multi
## High-Level Architecture
-```
-┌──────────────────────────────────────────────────────────┐
-│ trx-server │
-│ │
-│ Radio Hardware (serial/TCP) │
-│ ↕ CAT protocol │
-│ Rig Backend ──────── rig_task.rs ─── listener.rs │
-│ (ft817/ft450d/sdr) (state machine) (JSON TCP :4530) │
-│ │ │
-│ audio.rs │
-│ (Opus :4531) │
-│ │ │
-│ Decoders │
-│ (APRS, CW, FT8, WSPR, RDS) │
-│ PSKReporter / APRS-IS │
-└──────────────────────────────────────────────────────────┘
- ↕ JSON TCP (port 4530)
- ↕ Opus audio TCP (port 4531)
-┌──────────────────────────────────────────────────────────┐
-│ trx-client │
-│ │
-│ remote_client.rs (polls state, routes commands) │
-│ ↕ internal mpsc/watch channels │
-│ Frontends: │
-│ trx-frontend-http (Web UI :8080) │
-│ trx-frontend-rigctl (rigctl :4532) │
-│ trx-frontend-http-json (JSON/TCP ephemeral) │
-└──────────────────────────────────────────────────────────┘
- ↕ Browser / Hamlib / Custom tools
- End Users
+```mermaid
+graph TD
+ subgraph server["trx-server"]
+ HW["Radio Hardware"] <-->|"CAT protocol
serial / TCP"| Backend["Rig Backend
(ft817 / ft450d / sdr)"]
+ Backend --> RigTask["rig_task.rs
(state machine)"]
+ RigTask --> Listener["listener.rs
(JSON TCP :4530)"]
+ RigTask --> Audio["audio.rs
(Opus :4531)"]
+ Audio --> Decoders["Decoders
(APRS, CW, FT8, WSPR, RDS)"]
+ Decoders --> Uplinks["PSKReporter / APRS-IS"]
+ end
+
+ subgraph client["trx-client"]
+ Remote["remote_client.rs
(polls state, routes commands)"]
+ Remote <-->|"mpsc / watch channels"| HTTP["trx-frontend-http
(Web UI :8080)"]
+ Remote <-->|"mpsc / watch channels"| Rigctl["trx-frontend-rigctl
(rigctl :4532)"]
+ Remote <-->|"mpsc / watch channels"| JSON["trx-frontend-http-json
(JSON/TCP)"]
+ end
+
+ Listener <-->|"JSON TCP :4530"| Remote
+ Audio -->|"Opus TCP :4531"| Remote
+
+ HTTP & Rigctl & JSON <--> Users["End Users
(Browser / Hamlib / Custom tools)"]
```
The server and client are separate binaries. They communicate over **JSON-over-TCP** (control) and **Opus-encoded TCP** (audio). Both binaries can load shared-library plugins at startup.
diff --git a/docs/Wxsat-Map-Overlay.md b/docs/Wxsat-Map-Overlay.md
index c807698..edbf997 100644
--- a/docs/Wxsat-Map-Overlay.md
+++ b/docs/Wxsat-Map-Overlay.md
@@ -27,16 +27,16 @@ can be projected as semi-transparent overlays on the same Leaflet map.
### Data flow
-```
-Pass decoded (APT / LRPT)
- ↓ finalize_wxsat_pass / finalize_lrpt_pass (trx-server/audio.rs)
- ↓ SGP4 propagation using satellite TLE + pass timestamps
- ↓ Compute geo_bounds [[south, west], [north, east]]
- ↓ Compute ground_track [[lat, lon], ...]
- ↓ Attach to WxsatImage / LrptImage
- ↓ Broadcast via DecodedMessage
- ↓ SSE → browser
- ↓ wxsat.js: L.imageOverlay() + L.polyline() on aprsMap
+```mermaid
+graph TD
+ A["Pass decoded (APT / LRPT)"] --> B["finalize_wxsat_pass / finalize_lrpt_pass
(trx-server/audio.rs)"]
+ B --> C["SGP4 propagation using satellite TLE + pass timestamps"]
+ C --> D["Compute geo_bounds
[[south, west], [north, east]]"]
+ D --> E["Compute ground_track
[[lat, lon], ...]"]
+ E --> F["Attach to WxsatImage / LrptImage"]
+ F --> G["Broadcast via DecodedMessage"]
+ G --> H["SSE → browser"]
+ H --> I["wxsat.js: L.imageOverlay() + L.polyline() on aprsMap"]
```
### Geo-referencing strategy
diff --git a/src/decoders/trx-wxsat/README.md b/src/decoders/trx-wxsat/README.md
index 8560fe4..25fb88f 100644
--- a/src/decoders/trx-wxsat/README.md
+++ b/src/decoders/trx-wxsat/README.md
@@ -34,17 +34,11 @@ trx-wxsat/src/
### NOAA APT
-```
-FM-demodulated audio (any sample rate)
- │ AptDemod: FFT Hilbert transform, bandpass @ 2400 Hz ±1040 Hz
- ▼
-AM envelope resampled to 4160 Hz
- │ SyncTracker: 1040 Hz sync-A marker correlation
- ▼
-Aligned 2080-sample lines → [SyncA 39][SpaceA 47][ImageA 909][TelA 45][SyncB 39][SpaceB 47][ImageB 909][TelB 45]
- │ Telemetry extraction, wedge-based radiometric calibration, histogram EQ
- ▼
-PNG image (1818 × N pixels, dual-channel side-by-side)
+```mermaid
+graph TD
+ A["FM-demodulated audio
(any sample rate)"] -->|"AptDemod: FFT Hilbert transform,
bandpass @ 2400 Hz ±1040 Hz"| B["AM envelope
resampled to 4160 Hz"]
+ B -->|"SyncTracker: 1040 Hz
sync-A marker correlation"| C["Aligned 2080-sample lines
[SyncA 39][SpaceA 47][ImageA 909][TelA 45]
[SyncB 39][SpaceB 47][ImageB 909][TelB 45]"]
+ C -->|"Telemetry extraction,
wedge-based radiometric calibration,
histogram EQ"| D["PNG image
(1818 x N pixels, dual-channel side-by-side)"]
```
**Key DSP details:**
@@ -62,20 +56,12 @@ PNG image (1818 × N pixels, dual-channel side-by-side)
### Meteor-M LRPT
-```
-Baseband samples (any sample rate)
- │ QpskDemod: Costas loop carrier recovery + Gardner TED symbol timing
- ▼
-Soft symbols (±1.0, I/Q interleaved) @ 72 ksym/s
- │ CaduFramer: hard-decision, ASM (0x1ACFFC1D) search, frame lock
- ▼
-1024-byte CADUs (CCSDS transfer frames)
- │ ChannelAssembler: VCID → APID routing, MCU extraction
- ▼
-Per-APID pixel rows (1568 px wide)
- │ RGB composite (APIDs 64/65/66) or grayscale fallback
- ▼
-PNG image (1568 × N pixels)
+```mermaid
+graph TD
+ A["Baseband samples
(any sample rate)"] -->|"QpskDemod: Costas loop carrier recovery
+ Gardner TED symbol timing"| B["Soft symbols (±1.0, I/Q interleaved)
@ 72 ksym/s"]
+ B -->|"CaduFramer: hard-decision,
ASM (0x1ACFFC1D) search, frame lock"| C["1024-byte CADUs
(CCSDS transfer frames)"]
+ C -->|"ChannelAssembler:
VCID → APID routing, MCU extraction"| D["Per-APID pixel rows
(1568 px wide)"]
+ D -->|"RGB composite (APIDs 64/65/66)
or grayscale fallback"| E["PNG image
(1568 x N pixels)"]
```
**LRPT channel mapping:**