The Operator’s Cockpit is a 14-part deep dive into how you actually drive a running GopherTrunk — from a phone browser against a Raspberry Pi, or from a full-screen terminal over SSH. The engine decodes and records on its own; this series is about the two front-ends that let a human watch it, listen to it live, and change it safely — a React SPA baked into the single Go binary and a Bubbletea TUI, both speaking the same REST + Server-Sent-Events API.

Where Recording, Composition & Streaming covered turning a decoded call into a file and a live audio feed, this series is the surface on the other end of that feed: the live audio cockpit with its AudioWorklet ring buffer, the spectrum and constellation canvases, the map, and write-mode config. And where RF Scope ended on a “scene cockpit,” this is the cockpit pattern generalized — one API contract, two renderers.

Every post reads three ways: a TL;DR + cheat-sheet for skimmers, bold headers, tables, and diagrams for the medium read, and full prose with real code for the deep read.

New here? The Web console and TUI operator docs show the finished surfaces; this series is how they’re built.<ol class="post-list series-list"><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 1: One Engine, Many Front-Ends</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How one running GopherTrunk daemon exposes a single REST + Server-Sent-Events contract that both a React SPA in the browser and a Bubbletea terminal TUI render, kept honest by an api package that talks only to interfaces and never imports the SDR pool.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 2: A React SPA Inside a Go Binary</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk embeds its built Vite/React bundle into the daemon with go:embed all:dist, serves it at the root with a client-side-routing SPA fallback, degrades to a helpful message when the embed is empty, and stays a plain API client so browser and standalone deployments are identical.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 3: The Connect Screen & Auth Handshake</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How a browser pairs with a GopherTrunk daemon — the connect screen that probes health before saving, the three-mode bearer-token policy with loopback trust, the mutation gate, and the capability probe that lets a front-end grey out write controls before it ever tries them.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 4: The Event Stream — SSE to React</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk fans every internal bus event to clients as one JSON envelope — Server-Sent Events for the terminal and curl, a WebSocket twin for the browser that can’t set an auth header — with jittered reconnect backoff, frame coalescing, and interval poll hooks that only re-render on real change.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 5: The Live Audio Cockpit</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk streams live decoded voice to the browser — a publisher that fans PCM to bounded per-subscriber channels, a continuous open-ended WAV body served over fetch so the auth header rides along, and an AudioWorklet ring buffer that emits silence on underrun for gapless playback.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 6: Client-Side Resampling</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk plays 8 kHz voice on a 48 kHz browser AudioContext — a windowed-sinc polyphase resampler that carries a history tail and fractional cursor across network chunks, per-phase unity-gain normalized, band-limiting the images that linear interpolation leaks, with a split-feed-equals-whole-feed test as its keystone.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 7: Live Spectrum & Waterfall in the Browser</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk streams FFT frames from the SDR broker over a WebSocket and paints a scrolling waterfall plus analyzer trace on a canvas — a per-request FFT producer, a rate-clamped frame protocol, ImageData row rendering, and an FFT-shifted x-to-frequency mapping that powers hover readout and click-to-tune.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 8: Constellation, Eye Diagram & Symbol Scope</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How one WebSocket symbol stream off the daemon feeds three browser DSP scopes — a complex constellation, a folded C4FM eye diagram, and a rolling symbol oscilloscope — with server-side offset tuning to dodge the DC spike and a soft-vs-dibit frame shape shared across all three canvases.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 9: The Map — Plotting Sites & Emitters</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk plots P25 sites and position-bearing emitters — APRS, AIS, ADS-B, DSC — on one shared Leaflet map, patching markers in place from the daemon’s locations and sites REST endpoints as fixes arrive, with an auto-fitting camera and a merged discovered-plus-configured site table.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 10: Write Mode — Mutating Config Safely</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk lets a browser or terminal change a running daemon without lying about what took effect — the mutation-capability gate, pointer-field PATCH semantics that preserve leave-alone, the hot-reload vs restart-required split, an mtime conflict guard, and the validate-then-activate config swap.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 11: The Mobile Shell, Command Palette & PWA</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How one React shell serves a desktop sidebar and a phone’s bottom nav from a single nav registry, adds a ⌘K command palette as the fastest path to any panel, and installs as an offline-first PWA on a phone — with a service-worker denylist so the console never shadows its sibling apps.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 12: The TUI Cockpit — The Same API in Your Terminal</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk’s Bubbletea TUI drives the identical REST + SSE API as the browser — an Elm-model root reducer fanning out polling commands plus a long-lived SSE pump into one SharedState, thirteen panels as pure renderers over it, and a tab strip that hides the same web.tabs panels the web SPA does.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 13: The Reflect-Driven Config Form</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How one reflection walk over config.Config plus one shared field-metadata registry generates the whole config editor for both the terminal Bubbletea form and the web Config Builder — the TUI reading the registry directly, the web reading it over a fieldmeta endpoint, and two drift-guard tests keeping both editors honest to the struct.</p></li><li class="post-card"> <h2 class="post-card__title">The Operator’s Cockpit, Part 14: Testing Browser & Terminal UIs</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">The finale — how GopherTrunk tests a browser and a terminal operator UI without a live radio, standing the whole cockpit up in memory — httptest API contract tests, a byte-for-byte WAV header pinned on both the Go server and the TypeScript client, Bubbletea model tests that feed synthetic messages into the reducer, and pure SSE-parser and audio-framer unit tests.</p></li></ol><p class="blog-feed-link"> See all deep dives or subscribe via RSS. </p>