History persistence lives in trx-server, not trx-client.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Move persistent history from trx-client to trx-server where decode
events originate. History for AIS, VDES, APRS, CW, FT8, and WSPR is
loaded from ~/.local/cache/trx-rs/history.db at startup and flushed
to disk every 60 seconds. CW events are now also stored in
DecoderHistories and replayed to connecting clients, consistent with
all other decoder types.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add pickledb-backed persistent history store for all decoder types
(AIS, VDES, APRS, CW, FT8, WSPR). History is loaded from
~/.local/cache/trx-rs/history.db at startup and flushed to disk
every 60 seconds. On load, entry timestamps are reconstructed from
stored Unix ms values so 24h pruning continues to work correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Two problems prevented reliable recovery after a persistent overflow:
1. Restart storm: once read_error_streak >= 3, every subsequent read
failure triggered a deactivate/activate cycle, potentially preventing
the hardware from stabilising. Fix: after a successful restart, reset
read_error_streak to 1 so the stream gets 2 more failures before the
next restart attempt.
2. Stuck-deactivated stream: if activate() failed after overflow, the
stream was left deactivated. Subsequent reads returned non-overflow
errors which handle_read_error ignored (Ok(false)), so the stream was
never reactivated. Fix: add a high-streak fallback (>= 10 consecutive
errors of any kind) that also attempts a full deactivate/activate
restart, covering the stuck-deactivated case.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Two bugs triggered by a SoapySDR IQ overflow:
1. Spectrum dies permanently (trx-client): when GetSpectrum times out
(300ms SPECTRUM_IO_TIMEOUT), the error was silently swallowed and
the spectrum buffer cleared. The in-flight response remained in the
TCP receive buffer, desynchronising all subsequent reads so every
poll kept failing. Fix: propagate the error so handle_connection
returns and the outer loop reconnects, restoring TCP sync.
2. CTRL+C hangs trx-server: after IQ overflow, the sdr-iq-read thread
can get stuck in a blocking SoapySDR/USB call (deactivate/activate
with no timeout). Tokio received SIGINT and aborted async tasks, but
the process could not exit while the native thread was blocked in
uninterruptible I/O. Fix: call std::process::exit(0) after the
graceful shutdown sequence so the OS forcibly terminates all threads.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Server emits an SSE sentinel event (history_done) after replaying
stored history. Client buffers all incoming messages until the sentinel
arrives, then drains the buffer in 30-event chunks via setTimeout so
the browser can handle input between batches. Live events after the
sentinel are dispatched immediately as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Prepend the in-progress line to the bar render so characters appear
immediately rather than waiting for a newline or 5s gap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Show a live decode bar on the overview strip when in CW/CWR mode,
matching the APRS and AIS bar pattern. Accumulates decoded characters
into lines (split on newline events or >5s gaps), keeps a 15-minute
rolling history, and shows up to 8 recent lines with timestamp and
WPM/tone metadata. Clears on resetCwHistoryView.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add cwAutoLocalOverride flag in cw.js to block server-state snapshots
from overriding the checkbox while a user-initiated POST is in-flight.
Expose applyCwAutoUiFromServer for app.js render() to call instead of
applyCwAutoUi, preventing a racing SSE event carrying the old cw_auto
value from immediately undoing the user's toggle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
- Reduce analysis window from 50ms to 10ms so the decoder can detect
dits at 25+ WPM (at 25 WPM a dit is 48ms, shorter than the old window)
- Fix dot/dash classification threshold from 2.0× to 1.5× unit_ms;
ITU Morse dah = 3× dit, so the midpoint boundary is 1.5×
- Replace O(n) on_durations.remove(0) with drain() to trim the window
- Remove pointless emit_text() wrapper; callers now call emit_event()
directly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add per-channel processing gating and disable hidden AIS channel DSP unless mode is AIS or MARINE, reducing continuous IQ read-loop CPU load in normal operation.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add click-to-tune behavior on overview waterfall matching spectrum interactions, restore bookmark marker lines in the overlay using category colors, and keep current-tuned frequency marker visually distinct.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Skip IQ fanout buffer cloning when no subscribers exist and apply backoff on repeated zero-length reads to avoid hot-loop CPU spikes.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Ensure overview waterfall incremental updates continue on HiDPI and anchor bookmark chips to the top of the full spectrum view (waterfall + waveform).\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Improve WebGL runtime performance by caching/downsampling overview waterfall texture updates and batching marker/dashed-line draws; keep bookmark chips anchored at the top of the waterfall area.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace Canvas2D rendering in spectrum, overview, signal overlay, and CW tone picker with a shared WebGL renderer and wire the new asset into frontend HTTP routes.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add a right-side slider to control the waterfall/waveform split and\npersist the selected ratio locally.\n\nRework spectrum height layout so manual resize adjusts total plot height\nwhile split controls the overview/spectrum ratio.\n\nKeep center-frequency arrows and side bookmark stacks vertically centered\nwithin the full spectrum view container.\n\nCo-authored-by: Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Make the spectrum resize grip easier to use and style it closer to\nexisting controls.\n\nKeep auto-max behavior by default while allowing manual drag to\nresize beyond viewport fill, enforcing only the minimum height.\n\nCo-authored-by: Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Make spectrum plot use maximum available viewport height by default.
Add draggable vertical resize grip with a reasonable minimum height
and double-click reset back to auto-max.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
When mode is set to CW/CWR, force cw_decode_enabled=true in state
application to avoid stale disabled decode state.
Also route initial mode setup through apply_mode.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>