# UX Guidelines

This document captures the UI/UX design patterns, conventions, and principles observed across
the trx-rs application. It covers the web frontend, CLI interfaces, configuration wizard, API
design, and error handling.

*Last reviewed: 2026-03-28*

---

## 1. Web Frontend (trx-frontend-http)

### 1.1 Layout and Navigation

The web UI is a single-page application served from embedded assets (no build step). It uses
a **tab-based** navigation model with six top-level tabs:

| Tab | Icon | Purpose |
|---|---|---|
| **Main** | House | Primary radio control: spectrum, frequency, mode, PTT, VFO, SDR controls |
| **Bookmarks** | Bookmark | Saved frequency/mode presets with folder organisation |
| **Digital modes** | Bar chart | FT8/FT4/FT2, WSPR, CW, APRS, AIS, VDES decode tables |
| **Map** | Pin | Leaflet map for APRS/AIS/FT8 station plotting |
| **Settings** | Wrench | Scheduler, background decode, history retention |
| **About** | Info circle | Server/client/radio/audio/decoder/integration details |

Tabs use inline SVG icons with a text label below. On narrow viewports the tab bar wraps and
subtitles collapse to save space.

The **Settings** and **About** tabs each use a secondary **sub-tab bar** for further grouping
(e.g. Settings > Scheduler | Background Decode | History).

### 1.2 Theming

The UI supports **dark mode** (default) and **light mode** toggled via a header button. Theme
preference persists in `localStorage`.

Additionally, nine **colour styles** are available via a dropdown:

- Original (default), Arctic, Lime, Contrast, Neon Disco, Donald (golden-rain), Amber, Fire, Phosphor

Each style provides a full CSS custom-property override set for both dark and light variants.
Styles are applied via `data-style` and `data-theme` attributes on `<html>`.

All colours reference CSS custom properties (`--bg`, `--card-bg`, `--text`, `--accent-green`,
`--border-light`, etc.) so components never use hard-coded colour values.

### 1.3 Typography

- **Body**: `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`
- **Frequency display**: `DSEG14 Classic` (14-segment display font, loaded from CDN with `preload`)
- **Labels**: uppercase, 0.68-0.78 rem, `font-weight: 700`, `letter-spacing: 0.04em`
- **Section labels** use pill-shaped badges (`border-radius: 999px`) with muted text

### 1.4 Responsive Design

Six breakpoints handle layout adaptation:

| Breakpoint | Behaviour |
|---|---|
| `> 1100px` | Full width with bookmark side gutters on spectrum |
| `< 1100px` | Side bookmark panels hidden |
| `< 900px` | Card fills viewport width, reduced padding |
| `< 760px` | Tab bar wraps, controls stack vertically, safe-area-inset padding for notched devices |
| `< 640px` | Bottom-fixed tab bar (mobile), subtitles hidden, compact header |
| `< 520px` | Further compact adjustments |

Touch-specific: `@media (hover: none) and (pointer: coarse)` enlarges hit targets.

The spectrum panel hints adapt: mouse users see "Scroll to zoom / Ctrl+Scroll to tune /
Drag to pan" while touch users see "Pinch to zoom / Drag to pan".

### 1.5 Interactive Controls

- **Jog wheel**: Circular CSS-styled draggable dial for frequency tuning (skeuomorphic radial-gradient, grab cursor, shadow/inset). Plus/minus buttons flank it.
- **Step unit buttons**: Segmented button group (MHz / kHz / Hz) with `.active` highlight
- **Step scale**: 1x / 0.1x multiplier toggle
- **Frequency input**: Monospace DSEG14 font, editable `<input>` with disabled opacity fix
- **Mode selector**: `<select>` dropdown populated from rig capabilities
- **PTT / Power / Lock buttons**: Three-column grid in the transmit/power section
- **VFO picker**: Button group (horizontal on desktop, vertical stack on mobile)
- **WFM/SAM controls**: Compact labelled controls (de-emphasis, audio mode, denoise, stereo pilot flag, CCI/ACI interference bars)
- **SDR settings row**: AGC checkbox, RF/LNA gain inputs with Set buttons, noise blanker

### 1.6 Spectrum and Waterfall

The spectrum panel uses `<canvas>` elements (WebGL renderer optional) and offers:

- **Drag to pan**, **scroll to zoom**, **Ctrl+scroll to tune**
- Bandwidth edges are draggable to resize the filter
- Keyboard shortcuts: `+`/`-` zoom, arrows pan, `0` reset
- **Minimap** for orientation when zoomed
- **Resize grip** to adjust spectrum height
- Controls: bandwidth input, auto-BW, sweet-spot, peak hold (0-60s), floor (dB), range (dB), auto-level, contrast gamma slider
- **Waterfall/waveform split slider** (20%-80%, default 50/50)
- **Bookmark axis** overlays on left/right sides at wider viewports
- **Decoder overlays**: RDS station name, AIS/VDES/FT8/APRS/CW bar overlays using `aria-live="polite"`

### 1.7 Real-Time Data

- **SSE (Server-Sent Events)** on `/events` for rig state updates. Each SSE session gets a
  UUID, enabling per-tab rig selection without interfering with other tabs.
- **Named events**: `data` (state), `session` (session UUID), `channels` (virtual channels),
  `b` (spectrum bins as base64), `rds`, `vchan_rds`, `ping` (5-second heartbeat)
- **WebSocket** on `/audio` for Opus-encoded RX audio streaming
- **Connection lost banner**: `#server-lost-banner` with pulsing dot, text "trx-server
  connection lost -- waiting for reconnect", uses `aria-live="assertive"`
- **Loading state**: Centered "Initializing (rig)..." with subtitle, content hidden until ready

### 1.8 Accessibility

- All interactive elements have `aria-label` attributes
- Spectrum overlays use `aria-live="polite"` for screen reader announcements
- Connection-lost banner uses `aria-live="assertive"`
- `aria-hidden="true"` on decorative canvases and visual-only elements
- SVG icons include `aria-hidden="true"` with descriptive labels on parent buttons
- Spectrum resize grip has both `title` and `aria-label`

### 1.9 Authentication UX

When auth is enabled, an **auth gate** blocks the UI with:

- Title: "Access Required"
- Subtitle: "Sign in to continue"
- Username and password inputs + Login button (green accent, full-width)
- Error message area (red `#ff6b6b`)
- Role badge display

**Guest** provides read-only station access and is exclusive. Non-Guest accounts
may combine **Read**, **Control**, **Transmit**, **Write**, and **Administrator** roles.
Administrator implies all permissions.

Session cookie: `trx_http_sid`, HttpOnly, configurable Secure and SameSite attributes.

The header shows a Login/Logout button when auth is enabled (`#header-auth-btn`).

### 1.10 Multi-Rig Support

- **Header rig switcher**: `<select>` dropdown in the top bar for switching between connected rigs
- Per-tab rig binding: each SSE session independently selects a rig via `?remote=` query parameter
- Rig state isolation: only the disconnected rig shows the connection-lost banner
- About tab shows active rig, available rigs list

---

## 2. REST API Design

### 2.1 Conventions

- **Read operations** use `GET` (e.g. `/status`, `/events`, `/decode/history`, `/rigs`, `/bookmarks`)
- **Mutations** use `POST` for actions and toggles (e.g. `/set_freq`, `/toggle_power`, `/toggle_ft8_decode`)
- **CRUD resources** use proper verbs: `GET /bookmarks`, `POST /bookmarks`, `PUT /bookmarks/{id}`,
  `DELETE /bookmarks/{id}`
- **Batch operations**: `POST /bookmarks/batch_delete`, `POST /bookmarks/batch_move`
- **Nested resources**: `/channels/{remote}/{channel_id}/subscribe`, `/scheduler/{remote}/status`
- Responses are JSON with `Content-Type: application/json`
- SSE stream uses `Content-Type: text/event-stream` with `no-cache` and `keep-alive` headers

### 2.2 Request Timeout

All rig command requests have a **15-second timeout** (`REQUEST_TIMEOUT`). If the command
doesn't complete in time, the request returns an error rather than hanging.

### 2.3 Error Responses

- `401 Unauthorized`: `{"error": "Invalid credentials"}` or `{"error": "Authentication required"}`
- `429 Too Many Requests`: `{"error": "Too many login attempts, please try again later"}`
- `404 Not Found`: Auth endpoints when auth is disabled
- `500 Internal Server Error`: Serialization failures
- Rate limiting: 10 attempts per 60-second window per IP, counter resets on successful login

### 2.4 State Enrichment

API responses merge rig state with **frontend metadata** (`FrontendMeta`) via `serde(flatten)`:

```
http_clients, rigctl_clients, audio_clients, rigctl_addr,
active_remote, remotes[], owner_callsign, owner_website_url,
owner_website_name, ais_vessel_url_base, show_sdr_gain_control,
initial_map_zoom, spectrum_coverage_margin_hz, spectrum_usable_span_ratio,
decode_history_retention_min, server_connected
```

This single-payload approach avoids extra round trips for UI configuration.

---

## 3. CLI Interface

### 3.1 Argument Style

Both `trx-server` and `trx-client` use **clap** for argument parsing with short and long flags:

```
-C, --config FILE           Path to configuration file
--print-config              Print example configuration and exit
-r, --rig NAME              Rig backend name
-l, --listen ADDR           Listen address
-p, --port NUM              Port number
```

Positional arguments are used sparingly (e.g. `RIG_ADDR` for serial/TCP address).

### 3.2 Configuration Resolution

Config files are searched in priority order:
1. Current directory: `trx-rs.toml`
2. XDG config: `~/.config/trx-rs/trx-rs.toml`
3. System: `/etc/trx-rs/trx-rs.toml`

The loaded config path is logged: `INFO Loaded configuration from /path/to/config.toml`

### 3.3 Example Config Generation

`--print-config` outputs a complete, commented TOML file to stdout with example values
(callsign `N0CALL`, coordinates `52.2297, 21.0122`). Each section has a header comment and
each field has an inline description.

### 3.4 Startup Log Sequence

Server:
```
INFO Loaded configuration from /path/to/config.toml
INFO Starting trx-server with N rig(s): [rig-names]
INFO Callsign: CALL
INFO [rig-id] Starting (rig: ft817, access: serial /dev/ttyUSB0 @ 9600 baud)
INFO Listening on 0.0.0.0:4530
```

Client:
```
INFO Loaded configuration from /path/to/config.toml
INFO Starting trx-client (remotes: [remote-names], frontends: http,rigctl)
INFO rigctl frontend for rig 'default' on 127.0.0.1:4532
```

---

## 4. Configuration Wizard (trx-configurator)

### 4.1 Interactive Mode

Uses the **dialoguer** crate for terminal prompts:

- `Select` menus for enumerated choices (config type, rig model, access type, log level)
- `Input` for free-text with defaults (callsign defaults to `N0CALL`, listen defaults to `127.0.0.1`)
- `Confirm` for yes/no questions (enable auth, set location, etc.)
- Serial port auto-detection with fallback to `/dev/ttyUSB0`

### 4.2 Non-Interactive Mode

`--defaults` generates a config file without prompts, using sensible defaults.

### 4.3 Config Validation

`--check FILE` validates an existing config file:

```
/path/to/config.toml: valid TOML
  Detected type: server
  warning: [general].log_level 'verbose' is invalid (expected: trace, debug, info, warn, error)
  1 warning(s), 0 error(s)
```

Validates: TOML syntax, unknown keys, log levels, coordinate ranges (-90..90 lat, -180..180 lon
with pair requirement), access types, port ranges (0-65535).

### 4.4 File Write Confirmation

Prompts before overwriting an existing file. Outputs `Wrote /path/to/file` on success.

---

## 5. Error Handling and User-Facing Messages

### 5.1 Error Message Conventions

- **Contextual**: Include file paths, section names, and peer addresses
  - `"Failed to parse config file /path: error details"`
  - `"Unknown rig model: X (available: ft817, ft450d, soapysdr)"`
- **Actionable**: Suggest alternatives when available
  - `"Rig model not specified. Use --rig or set [rig].model in config."`
  - `"Unknown frontend: X (available: http, rigctl, httpjson)"`
- **Structured**: Use field=value format in structured logging

### 5.2 Log Level Guidelines

| Level | Usage |
|---|---|
| `INFO` | Startup milestones, configuration loaded, listening, client connect/disconnect, decoder state changes |
| `WARN` | Non-fatal issues: command took too long, panel lock blocking, VFO priming failed, initial tune failed |
| `ERROR` | Fatal or significant failures: CAT polling errors, client errors, parse failures |

Logs suppress module targets (`with_target(false)`) for cleaner output.

### 5.3 Connection State Communication

- Server logs: `"Client connected: {peer}"`, `"Client {peer} disconnected"`, `"Client {peer} closing due to shutdown"`
- Rig task: `"[rig-id] Rig backend ready"`, `"Serial: /dev/ttyUSB0 @ 9600 baud"`
- Web UI: Connection-lost banner with reconnect indication, per-rig isolation

### 5.4 Graceful Degradation

- Startup continues after non-fatal failures: `"Initial PowerOn failed (continuing)"`
- Stream errors are deduplicated with 60-second summaries to avoid log flooding
- Lock poisoning is recovered from rather than panicking
- Unknown SSE events or lagged broadcast channels are silently skipped

---

## 6. Branding and Customisation

### 6.1 Owner Branding

Configurable via TOML and exposed via `FrontendMeta`:

- `owner_callsign` -- displayed in header subtitle and About tab
- `owner_website_url` / `owner_website_name` -- optional link in header
- `ais_vessel_url_base` -- base URL for linking AIS vessel MMSI numbers

### 6.2 UI Behaviour Configuration

- `http_show_sdr_gain_control` -- show/hide RF gain controls
- `http_initial_map_zoom` -- default map zoom level
- `http_spectrum_coverage_margin_hz` -- guard margin for spectrum center retune
- `http_spectrum_usable_span_ratio` -- fraction of spectrum span treated as usable
- `http_decode_history_retention_min` -- default history retention (per-rig overrides supported)

### 6.3 Embedded Assets

Logo and favicon are embedded at compile time via `include_bytes!`. The logo image has an
`onerror` handler to hide itself if loading fails (`this.style.display='none'`).

---

## 7. Security UX

### 7.1 Route Access Classification

Routes are classified into access tiers:

| Tier | Examples | Requirement |
|---|---|---|
| **Public** | `/`, `/index.html`, `/map`, login/session endpoints, static assets | None |
| **Read** | `/status`, `/events`, `/audio`, `/decode`, `/spectrum`, `/bookmarks` | Guest, Read, Control, Transmit, or Administrator role |
| **Control** | `/set_freq`, `/set_mode`, `/toggle_power`, receive-side radio-control POST routes | Control or Administrator role |
| **Transmit** | `/set_ptt`, `/set_tx_limit`, outbound `/audio` frames | Transmit or Administrator role |
| **Write** | Logbook access and bookmark mutations | Write or Administrator role |

### 7.2 Session Management

- Sessions are 128-bit random hex tokens stored in HttpOnly cookies
- Configurable TTL (default from TOML config)
- Expired sessions auto-pruned on access
- Passwords are verified against salted Argon2id hashes

### 7.3 User Management

Every authenticated non-Guest account gets a Settings > Account tab for changing
its own password. Guest sees neither Account nor Users and both account-control
APIs deny Guest sessions. Only administrators get Settings > Users, where accounts can be
created, enabled/disabled, assigned multiple roles, given a new password, or
removed. The final enabled administrator cannot be disabled, removed, or
demoted. Account security changes revoke every active session for that account.

---

## 8. Virtual Channels (SDR)

Virtual channels allow SDR users to monitor multiple frequencies simultaneously:

- Channels appear in a picker row below the VFO controls
- CRUD API: `POST /channels/{remote}` to create, `DELETE` to remove, `PUT` to update freq/mode/BW
- Subscribe/unsubscribe audio per channel
- Background decode channels (hidden, no audio stream back)
- Channels auto-destroyed when out-of-bandwidth after center-frequency retune
- Channel-list changes broadcast to SSE clients via `event: channels`

---

## 9. Design Principles (Inferred)

1. **Server-rendered SPA**: All HTML/CSS/JS embedded in the binary -- zero external build tooling, no CDN dependency for core functionality (CDN used only for fonts and Leaflet maps).

2. **Progressive disclosure**: Advanced controls (WFM, SAM, SDR settings, spectrum controls) are hidden by default and revealed based on the active mode and backend type.

3. **Keyboard-first, touch-aware**: Spectrum supports full keyboard navigation alongside mouse and touch gestures. Mobile breakpoints enlarge hit targets and adapt layout.

4. **Real-time by default**: SSE + WebSocket provide sub-second state updates without polling from the browser. 5-second ping heartbeat detects stale connections.

5. **Per-tab isolation**: Each browser tab gets its own SSE session UUID and can independently select a rig, preventing cross-tab interference.

6. **Configuration over code**: UI behaviour knobs (gain visibility, map zoom, history retention, spectrum margins) are exposed as TOML config rather than requiring code changes.

7. **Graceful degradation**: The UI handles server disconnection gracefully with visible banners, and only the affected rig shows as disconnected in multi-rig setups.

8. **Defensive security defaults**: Auth disabled by default for ease of setup, but when enabled, provides role-based access, rate limiting, constant-time comparison, and HttpOnly cookies.
