RADIOCLI

Design

Product rationale for the receiver UI, terminal adaptation, playback backends, and provider adapters.

RadioCLI is meant to feel like a product, not a terminal proof of concept.

Why Ink

Ink gives the project React's component model while staying native to terminal constraints. That makes it easier to keep the app screen-oriented, testable, and incrementally adaptable as the UI grows.

Why mpv

Radio streams are messy: redirects, playlists, HLS, odd codecs, and flaky metadata are normal. mpv already handles that complexity well and exposes JSON IPC for controls and metadata.

ffplay and VLC provide playback fallbacks when mpv is unavailable. Their limited control and state reporting is reflected in the UI. mpv is the intended backend for pause, mute, volume, media keys, metadata, and readiness checks.

Why Provider Adapters

Public radio directories change, block, rate limit, and disagree. Keeping providers behind adapters prevents the UI from depending on one API shape.

Radio Browser is primary because it is public and has broad station metadata. Radio Garden stays experimental because its public web experience does not mean its API is stable for terminal clients.

Why Location Is Controllable

Nearby stations are useful, but IP-based location lookup is privacy-sensitive. The feature is enabled for new libraries and performs no lookup until Nearby is opened. It remains directly controllable through Settings, l, or :location off, and that local choice persists.

Receiver Aesthetic

The UI borrows from old receiver and car head-unit displays: direct labels, compact hierarchy, visible status, and no fake copy. The app should be usable in a terminal session, not a landing page rendered in ANSI.

On terminals with sufficient color support, the app shell paints a near-black background that keeps the receiver, tabs, stats panels, and footer visually cohesive. Transparent Background, limited-color terminals, and NO_COLOR let the terminal keep its own background.

The Now Playing screen supports multiple visualizer styles so the display can feel like a receiver without locking the product into one animation. The default Ultracode style is fast and direct, while the retained alternates include the restored spinning ASCII cube plus generated multi-flame fire, fireworks, spinning donut, starfield, high-resolution braille visuals, and generative motion scenes. Every retained style is deterministic, pure-frame terminal rendering and still freezes to a truthful zero-signal frame when playback is inactive.

Receiver motion is tied to playback truth rather than screen presence. Visualizers animate only when playback is playing and backend-ready; idle, loading, paused, stopped, and error states render a flat zero-signal frame so the display never implies live audio when nothing is playing.

Explore uses a dense braille world map next to the station list on wide terminals. That layout keeps global discovery spatial without forcing the user to switch away from the playable list. The map has one movable scan cursor instead of many station pins; clicking the map places it directly, WASD makes fine nudges, Shift+WASD makes larger jumps, and the station list reloads around that coordinate. Mouse reporting is activated only for Explore and overflowing menus or lists, then released on other screens so native terminal scrollback continues to work.

The Explore list is distance-truthful rather than popularity-first. RadioCLI builds a cached atlas from all available Radio Browser stations with coordinates and computes local distance inside the app. The list reports the radius covered by the returned stations so the user can tell whether a cursor position is dense, sparse, or limited by provider geotag coverage.

Terminal Adaptation

Full, compact, and micro layouts keep the current selection and keyboard actions available as space shrinks. Compact and micro views shorten labels and adjust the receiver and list areas. The selected station's full name remains available to screen-reader output.

Unicode and rich color are retained when the terminal supports them. Explicit ASCII settings or a bare C/POSIX locale replace decorative glyphs; UTF-8 locales and SSH sessions retain Unicode. ASCII mode preserves international station text. Sixteen-color terminals drop the dark fills, and NO_COLOR removes color entirely.

Reduce Motion stops receiver animation, loading animation, and scrolling labels. Screen-reader mode also omits receiver visualizers and mouse reporting while keeping keyboard controls and text status. TERM=dumb produces static frames with ASCII decoration and no color. These environment adaptations apply to the current launch and leave saved appearance preferences intact. The exact overrides are listed in TUI Controls.

Visual Regression Captures

The deterministic Ink snapshot suite covers every screen and settings group, all receiver styles at full size, representative receivers at smaller sizes, Unicode and ASCII decoration, output without color, and focused ANSI color and selection checks. The fixtures use these sizes:

LayoutColumns × rows
Full100 × 30
Compact50 × 16
Micro24 × 8

Time, timezone, station data, and animation frames are fixed so failures show real rendering differences. ASCII fixtures use ASCII only for decoration while preserving international station names. The snapshot suite verifies renderer output; native fonts, physical displays, audio, and desktop integration are outside its scope. See Platforms for native feature coverage.

The horizontal tab rail is intentionally dense: it keeps the app feeling like a terminal tool with real surfaces instead of a stack of disconnected menus. On wide terminals the full rail is visible; on narrower terminals it keeps the active tab and nearby tabs in view.

Listening stats use the same direct, boxed language as the rest of the app. The contribution graph begins in January and shows the current local calendar year, while the numeric stats report real locally persisted sessions, including favorite station, thresholded stations listened, sessions, streaks, active days, and total hours listened.

The stats graph palette follows the selected display color, so Stats feels like part of the same configured receiver surface.

On this page