postPath() was duplicating rig_id on /select_rig calls, causing deserialization failure and silently dropping the rig switch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
All POST command endpoints now accept an optional ?rig_id= parameter so each browser tab can independently target a specific rig. The JS frontend tracks the active rig per-tab and auto-appends rig_id to every request.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add checkbox column to bookmark table with select-all support and a
Delete Selected button for batch removal. New POST /bookmarks/batch_delete
API endpoint accepts an array of IDs and removes them in one request.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Broadcast virtual channel list for newly selected rig from select_rig so SSE
clients receive correct channels immediately. Detect rig changes in render()
and reset stale decoder state (RDS, spectrum, decoder status indicators) from
the previous rig.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add Maidenhead locator and reverse-geocoded city/country to the header.
Uses Nominatim API to resolve nearest city asynchronously.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
When Device::new(args) fails, enumerate all available SoapySDR devices and
include them in the error message. Also hint that args are case-sensitive.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
When specific SoapySDR device args are provided (e.g. with a serial number),
fail hard instead of silently falling back to Device::new("") which opens
the first available device. This caused multi-device setups to bind both
rig instances to the same physical device.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add weakest decoded signal panel showing top 5 weakest SNR signals. Make all
stat tiles (longest decode, strongest signal, weakest signal) clickable to
highlight the corresponding locator on the map.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Reflect the common/ft8/ft4/ft2 directory reorganization in the
architecture diagram, file tree, and signal flow description.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Move ft2_encode from ft4/ to ft2/ where it belongs. Remove all
module-level #[allow] suppressions and fix the underlying issues:
- Remove dead code: wf_mag_at, xor_rows, unused Monitor IFFT fields, OsdBox.size
- Gate encode174_to_bits with #[cfg(test)] (only used in tests)
- Convert 40+ C-style index loops to idiomatic iterators
- Add targeted #[allow(clippy::too_many_arguments)] on two OSD functions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Move ft2/osd.rs, ft2/bitmetrics.rs, ft2/downsample.rs, ft2/sync.rs
out of the ft2/ directory into src/ as top-level modules. Convert
ft2/mod.rs to ft2.rs. Update all imports from super:: to crate::ft2::.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace FTX_CRATE.md with README.md documenting upstream origins
(kgoba/ft8_lib for FT8/FT4, iu8lmc/Decodium-3.0 for FT2) and a
Mermaid diagram of the crate architecture.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Remove normalize_llr which was undoing the scalefac=2.83 scaling,
causing LLRs to be 2.83x too small for the BP+OSD decoder. Align
sync thresholds with reference: coarse 0.50->0.40, decode 0.65->0.55,
sync quality 10->9, maxosd 3->4. Revert norm_sqr back to norm in
bitmetrics since the metric difference is nonlinear.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace Vec<Vec<f32>> with flat stack arrays in ldpc_decode (~114KB),
convert 19+ Vec allocations to stack arrays in osd174_91, eliminate
per-call temp Vec in nextpat91 via in-place mutation, and replace
norm() with norm_sqr() in bitmetrics hot loop (~5.4M calls/frame).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Eliminate duplicated code between FT2 and FT8/shared modules:
- Share parity8() from encode.rs, remove copies in ft2/mod.rs and osd.rs
- Share pack_bits() from decode.rs, remove pack_bits91() from osd.rs
- Add verify_crc_and_build_message() to decode.rs, used by both FT8 and FT2
- Add normalize_llr() to decode.rs, replacing per-module normalization
- Make encode174() pub(crate), add encode174_to_bits() for bit-array output
- Wire FT2 decode_hit to use full BP+OSD decoder from osd.rs instead of
separate BP + sum-product + OSD-lite flow
- Align LLR scale factor to 2.83 matching reference implementation
Net -178 lines removed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
add() and lookup() had no wrap-around guard in their linear-probe loops.
Once 256 unique callsigns filled the table, any subsequent add or lookup
for an absent hash would cycle through all 256 slots forever, hanging the
FT8 decoder task permanently inside block_in_place. On a busy band this
could happen within a few minutes of operation.
- add(): evict the probe-start slot when a full cycle completes
- lookup(): return None after a full probe cycle
- reset(): call cleanup(10) each slot boundary to age out stale entries
- Add regression tests for both infinite-loop scenarios
Also includes cargo fmt reformatting of pre-existing style issues.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Reuse FT2 downsample and bitmetric work buffers, speed up\nsync2d_score with precomputed references, and cache peak-search\nFFT state on the pipeline.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Delete the obsolete ft8_lib submodule and update documentation to point at the pure Rust trx-ftx decoder.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Normalize tracked SPDX headers to the 2026 Stan Grams identity.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Quiet compiler and clippy warnings in the translated decoder modules.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Delete trx-ft8 (C wrapper around ft8_lib + ft2_ldpc) and update
trx-server to depend on trx-ftx (pure Rust) directly. Removes
~2,900 lines of C code and all unsafe FFI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace the C FFI-based trx-ft8 with a pure Rust implementation
supporting FT8, FT4, and FT2 protocols. Eliminates cc/libc build
dependencies and all unsafe FFI code while providing the same
Ft8Decoder/Ft8DecodeResult public API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Clicking a source chip now isolates that source instead of toggling it.
Clicking the already-isolated source restores default visibility.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Replace the fixed max-height: 360px on FT8/FT4/FT2/WSPR message
containers with flex-based layout so they grow to fill the available
space. Make #content, .tab-panel, and .sub-tab-panel flex containers
that propagate height down the layout chain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Thread aprs_is_status through RigState, RigSnapshot, and the protocol
layer following the same pattern as pskreporter_status. Show the
connection target and callsign when enabled, or "Disabled" otherwise.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Preserve the WebGL drawing buffers used by the spectrum snapshot,
flush them before compositing, and move the shortcut listener to
capture phase so focused widgets do not swallow it.
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
Query the device for AGC support via has_gain_mode and enable it
automatically at startup. Devices without hardware AGC fall back to
manual gain as before.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>