# 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 ``. 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 `` with disabled opacity fix - **Mode selector**: `` 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 three tiers: | Tier | Examples | Requirement | |---|---|---| | **Public** | `/`, `/index.html`, `/map`, `/auth/*`, static assets | None | | **Read** | `/status`, `/events`, `/audio`, `/decode`, `/spectrum`, `/bookmarks` | Rx or Control role | | **Control** | `/set_freq`, `/set_mode`, `/set_ptt`, `/toggle_power`, all other POST | Control role only | ### 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 - Constant-time passphrase comparison to mitigate timing attacks ### 7.3 TX Access Control An additional `tx_access_control_enabled` flag can restrict transmit-related actions even for Control-role users, providing an extra safety layer. --- ## 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.