Commit Graph
100 Commits
Author SHA1 Message Date
ClaudeandStan Grams 07a10c0c3b [fix](trx-frontend-http): remove decommissioned NOAA 15/18/19 satellite presets
NOAA 15, 18, and 19 APT satellites are decommissioned. Remove them
from the satellite preset list and update the placeholder text.

https://claude.ai/code/session_01VFLAHs1UMzPso3GWSQP9wJ
Signed-off-by: Claude <noreply@anthropic.com>
2026-04-01 09:51:58 +02:00
ClaudeandStan Grams 06f7c43799 [feat](trx-frontend-http): implement scheduler UI improvements (P0–P3)
Implement all 15 scheduler improvement tasks from docs/scheduler_improvements.md:

P0 — Usability Fixes:
- Highlight active entry in time-span table with sch-active class
- Bookmark existence validation on save with toast error
- Dirty-state indicator for satellite section via markDirty bridge

P1 — Information Density & Clarity:
- Show local time alongside UTC in entry table and timeline
- Expand entry details by default with localStorage persistence
- Richer "Now Playing" status card with freq, mode, active decoders

P2 — Interaction Improvements:
- Inline entry editing directly in table rows
- Drag-to-reorder entries with HTML5 drag-and-drop
- Timeline click-to-add with pre-filled hour range
- Improved extra-channels management with chip list and dropdown

P3 — Feature Enhancements:
- Grayline location lookup by Maidenhead grid square
- Expanded satellite preset library (NOAA 15/18/19, ISS, SO-50)
- Scheduler activity log with ring buffer backend and UI
- Timeline interleave visualization with alternating color stripes
- Keyboard shortcuts (Shift+R/N/P) for scheduler control

https://claude.ai/code/session_01VFLAHs1UMzPso3GWSQP9wJ
Signed-off-by: Claude <noreply@anthropic.com>
2026-04-01 09:51:58 +02:00
ClaudeandStan Grams c5ccac3a17 [docs](trx-rs): add scheduler UI improvement plan
Proposes 15 improvements across 4 priority tiers covering usability
fixes, information density, interaction improvements, and new features.

https://claude.ai/code/session_01QYM8TxGaNpNgW6FU5uEYmb
Signed-off-by: Claude <noreply@anthropic.com>
2026-04-01 09:16:09 +02:00
ClaudeandStan Grams 1fe0b75e20 [fix](trx-rs): fix LRPT pass detection status never updating during active decoding
The #sat-status element was stuck on "Waiting for satellite pass" because:

1. The client audio handler (audio_client.rs) did not include AUDIO_MSG_LRPT_IMAGE
   in its message type match, so LRPT image messages from the server were silently
   dropped and never reached the frontend.

2. No progress was reported during active LRPT decoding — the only status update
   happened when a complete image was finalized, which could take the entire pass.

3. The sat-status text was never updated when the decoder was enabled/disabled,
   leaving it permanently at the HTML default text.

Changes:
- Add DecodedMessage::LrptProgress variant for live MCU progress reporting
- Send LRPT progress updates from the decoder task when new MCUs are decoded
- Add AUDIO_MSG_LRPT_IMAGE and AUDIO_MSG_LRPT_PROGRESS to client audio handler
- Update sat-status text when decoder state changes (enabled/disabled)
- Handle lrpt_progress messages in the frontend to show "Receiving — N MCU rows"

https://claude.ai/code/session_017knbD7dr6hJGAWR6pModL7
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-31 16:21:12 +02:00
ClaudeandStan Grams f2469fee12 [fix](trx-wxsat): fix LRPT decoder always-enabled bug and implement image decoding
The LRPT decoder task was missing mode checks, processing audio in any
rig mode once toggled on. Now it only activates in FM mode, matching
the decoder registry descriptor. Also corrects active_modes from
DIG/USB to FM.

Replaces the MCU stub (which treated compressed JPEG data as raw
pixels) with proper Huffman + inverse-DCT decompression, CCSDS packet
reassembly from MPDUs, and CCSDS derandomization in the CADU framer.

https://claude.ai/code/session_0135LuveBndEiZHkU2jsKPB9
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-31 14:58:15 +02:00
ClaudeandStan Grams 5c43bac42b [feat](trx-rs): remap decoder modes — remove DIG/PKT from SDR, wire decoders to standard modes
For SDR backends, DIG and PKT are removed from supported_modes and
replaced by USB and FM respectively. CAT backends (FT-817, FT-450D)
retain DIG/PKT as before.

Decoder mode allowances updated:
- APRS: FM | PKT (was PKT only)
- HF-APRS: USB | DIG (was DIG only)
- AIS: AIS | FM | PKT (was AIS only)
- VDES: VDES | FM (was VDES only)
- FT8/FT4/FT2/WSPR: USB | DIG (unchanged)
- CW: CW | CWR (unchanged)
- LRPT: FM (unchanged, mode-independent)

Frontend status text, bookmark decoder toggles, background-decode
fallbacks, and scheduler wiring updated to match.

https://claude.ai/code/session_01DCAaMH8RF5FNB2gRtVu4pY
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 18:55:04 +02:00
ClaudeandStan Grams f944cc0790 [fix](trx-frontend-http): remove max-width constraint from settings panel
The scheduler/settings panel had a max-width: 900px that made it narrower
than the statistics panel which has no such constraint.

https://claude.ai/code/session_0151QL4vHke3z31jJKAtP1b1
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 18:10:03 +02:00
ClaudeandStan Grams 5e51990275 [feat](trx-frontend-http): add FM broadcast band (87.5-108 MHz) to bandplan
Add the FM broadcasting band to all three IARU regions so the
87.5-108 MHz range is visible in the bandplan overlay.

https://claude.ai/code/session_01XCmCtBud7riY5anZRvvK2p
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 11:10:44 +02:00
ClaudeandStan Grams 582f674b7a [feat](trx-frontend-http): add missing amateur bands for full 0-1 GHz coverage
Add 2200m (135.7-137.8 kHz), 630m (472-479 kHz), 4m (70-70.5 MHz,
R1 only), 1.25m (222-225 MHz, R2 only), and 33cm (902-928 MHz, R2
only) bands to bandplan.json across all applicable IARU regions.

https://claude.ai/code/session_01XCmCtBud7riY5anZRvvK2p
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 11:10:44 +02:00
ClaudeandStan Grams 7dfac0c38c [fix](trx-frontend-http): remove duplicate bandplan overlay from spectrum canvas
The bandplan strip was rendered twice: once as a DOM element above the
spectrum and again via WebGL directly on the spectrum canvas. Remove the
WebGL duplicate and keep only the DOM-based strip.

https://claude.ai/code/session_01TA1pCDuAr7V6oSnQs7JYvU
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 11:08:41 +02:00
ClaudeandStan Grams f6f59f3d00 [fix](trx-frontend-http): move band plan strip above waterfall in z-order
The band plan strip was visually positioned between the waterfall and
waveform areas. Move it to the top of .signal-visual-block (above the
overview/waterfall) so it renders above the waterfall. Remove the
bp-webgl transparent overrides since the strip now shows colored
segments in its own position rather than overlaying the spectrum canvas.

https://claude.ai/code/session_01KoxcohG6hn5b7kSc3mC4dA
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 10:35:25 +02:00
ClaudeandStan Grams 9dbf6fc64e [feat](trx-frontend-http): move band plan overlay to top of spectrum view
Repositions the bandplan strip from the bottom of the combined spectrum
canvas to the top. Updates HTML element order, CSS bp-webgl absolute
positioning, and WebGL rendering Y coordinates.

https://claude.ai/code/session_015sRhGsk7ggRYoxJANDY72S
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 10:11:43 +02:00
ClaudeandStan Grams b8f6208aa7 [fix](trx-frontend-http): move band plan strip above waterfall instead of between waveform and waterfall
Relocate the band plan strip from the top of the spectrum canvas to the
bottom, directly above the waterfall canvas. Move the DOM element inside
.spectrum-wrap before the waterfall canvas so it flows naturally in the
correct position. Remove the reparenting logic since the element is now
always inside .spectrum-wrap.

https://claude.ai/code/session_01FUD2eKgeXMFGhhYTzmA4Z6
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 10:00:42 +02:00
ClaudeandStan Grams 9188b8ae4f [fix](trx-frontend-http): remove max-width constraint on statistics panel
The statistics tab had max-width: 72rem (1152px) while its parent .card
container uses --card-base-max-width: 1280px. This made the stats panel
visibly narrower than the header. Removing the constraint lets the panel
fill the card width like all other tab panels.

https://claude.ai/code/session_01SfhMwN8YKKEdA3f3JyfwUZ
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 09:46:58 +02:00
ClaudeandStan Grams c85a9c9bc4 [feat](trx-frontend-http): implement Phase 1 UX/UI quick wins from Settings analysis
- IX-2: Add confirm() dialogs before all destructive actions (10 history
  clear buttons, scheduler reset, background decode reset)
- IX-6: Add Select All / Deselect All buttons for background decode
  bookmark checklist
- IX-1: Add dirty-state indicator (pulsing dot) on Save buttons when
  unsaved changes exist in scheduler and background decode panels
- A-4: Add role="alert" and aria-live="polite" to toast notification
  elements for screen reader accessibility
- A-3: Add Unicode symbol prefixes to background decode state labels
  (checkmark/triangle/cross) so state is distinguishable without color

https://claude.ai/code/session_01ShfPMW9hPLD3czp9YovkbJ
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 09:35:47 +02:00
ClaudeandStan Grams 8ea7bf3b84 [docs](trx-rs): add settings menu UX analysis and improvement plan
Comprehensive analysis of the HTTP frontend settings tab covering
information architecture, interaction design, visual layout,
and accessibility. Prioritized improvement plan in three phases.

https://claude.ai/code/session_013i8aoQinGF97afSe5qsJrn
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 09:20:59 +02:00
ClaudeandStan Grams 71bf8d9456 [style](trx-frontend-http): move bandplan strip from bottom to top of spectrum
Repositions the bandplan rendering above the waterfall instead of below
the spectrum waveform. Updates both the WebGL draw position (y=0) and
the CSS overlay positioning (top:0) for the label layer.

https://claude.ai/code/session_01Bt6iUi6Pc1v7yvLffEjweJ
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 08:34:09 +02:00
ClaudeandStan Grams 67f0d451b9 [fix](trx-frontend-http): guard initAprsMap against missing Leaflet variable
Prevent ReferenceError when navigating to the map tab before the
Leaflet CDN script has finished loading.

https://claude.ai/code/session_018nDze1zN1AR3UgYRx5pqcL
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 08:03:38 +02:00
ClaudeandStan Grams c3b9d2d6fd [fix](trx-frontend-http): close IIFE before bandplan strip declarations
The spectrum floor/gamma IIFE (line 11507) was missing its closing
`})();`, causing all bandplan strip variables and functions to be
trapped inside the IIFE scope. This made `bandplanRegion`,
`updateBandplanStrip`, and `_bandplanServerDefaultApplied` invisible
to the rest of the file, throwing ReferenceErrors that crashed
`render()` before the frequency display update could run — leaving
the frequency input stuck at its initial "--" placeholder.

https://claude.ai/code/session_01RgKhusmnk7AHEJqn1KHffU
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 08:00:06 +02:00
ClaudeandStan Grams 64682a900f [fix](trx-frontend-http): record decode statistics for all decoder types
The "Decodes by type" statistics panel only showed AIS because
statsRecordDecode was only called from dispatchDecodeMessage, which
was bypassed by two code paths:

1. dispatchDecodeBatch: uniform-type batches dispatched to specialized
   batch handlers (onServerFt8Batch, etc.) returned early without
   recording stats.

2. restoreDecodeHistoryGroup: history messages restored on page load
   were never recorded in the statistics log.

Fix both paths by recording stats up-front in dispatchDecodeBatch
before dispatching to batch handlers, and in restoreDecodeHistoryGroup
before restoring to plugin views. Add a skipStats parameter to
dispatchDecodeMessage to prevent double-counting when the fallback
per-message loop runs inside dispatchDecodeBatch. Also accept an
optional timestamp in statsRecordDecode so history entries use their
original ts_ms rather than Date.now().

https://claude.ai/code/session_01Ss2AD2bQgXu1ir1Z1WE3VY
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 07:54:47 +02:00
ClaudeandStan Grams 2150f61828 [feat](trx-frontend-http): add Statistics panel, move summaries from Map tab
Extract the three summary sections (longest decode paths, strongest/weakest
signals) from the Map tab into a new dedicated Statistics tab. Add new
analytics: decode counters, unique stations/grids, decode rate, decode-by-type
breakdown, band activity, per-receiver comparison, and DX distance histogram.
The Statistics panel has its own receiver and history filters independent of
the map view.

https://claude.ai/code/session_01R9T4Byg7uw6qpkTsyVJd9k
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 07:31:14 +02:00
ClaudeandStan Grams 8c5706f6c3 [feat](trx-client): add bandplan display config to client settings
Add bandplan_enabled (default: true) and bandplan_region (default:
"iaru_r1") fields to [frontends.http] config section, allowing the
operator to control the initial bandplan display setting from the
server config rather than requiring each browser session to configure
it manually. The server-provided default is applied on first connect
only when the user has no existing localStorage override.

https://claude.ai/code/session_01H7427hzbJepJzkoUJzoDmH
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 00:28:53 +02:00
ClaudeandStan Grams 3a2733850c [feat](trx-frontend-http): render bandplan strip via WebGL on spectrum canvas
Move bandplan segment rendering from DOM elements to WebGL, drawing
coloured rectangles at the bottom of the spectrum canvas (above the
waterfall). All segments are batched into a single drawTriangles() call
for efficiency. The DOM strip is reparented into .spectrum-wrap and
restyled as a transparent text-label overlay (bp-webgl class). Non-SDR
rigs without a spectrum canvas retain the original DOM-coloured fallback.

https://claude.ai/code/session_01XTizHhXbXSAPQVAf1j9CSF
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 00:13:32 +02:00
Claudeandsjg 9abd2b7748 [fix](trx-frontend-http): make bandplan strip visible for all rig types
Move the bandplan strip out of the SDR-only spectrum panel into the
always-visible signal-visual-block. Add bandplanComputeRange() that
derives a frequency range from the current tuned frequency and band
edges when no spectrum data is available (non-SDR rigs). Trigger
bandplan updates on frequency changes and from the overview draw loop.

https://claude.ai/code/session_01AyBktp6b8qFjchyyqwL7dv
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 23:59:08 +02:00
ClaudeandStan Grams 4c095e64f0 [feat](trx-frontend-http): add bandplan strip above spectrum waterfall
Add a bandplan display strip that shows IARU frequency allocations
(CW, Phone, Digital, FM, Beacon, Satellite) above the spectrum plot.
Includes IARU Region 1/2/3 data for all HF/VHF/UHF bands, a settings
submenu for region selection and label toggle, and color-coded segments
that pan/zoom with the spectrum view.

https://claude.ai/code/session_01AyBktp6b8qFjchyyqwL7dv
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 23:49:36 +02:00
ClaudeandStan Grams c041ac83f3 [refactor](trx-rs): resolve all improvement areas (P1–P3)
P1 — High:
- Merge duplicate APRS/HF-APRS decoder tasks into parameterised inner fn
- Merge duplicate FT8/FT4 decoder tasks into shared ftx inner fn
- Add multi-rig state isolation and command routing tests (listener.rs)
- Add background decode evaluate_bookmark unit tests

P2 — Medium:
- Fix decode-log silent flush errors and rotation failure fallback
- Split api.rs (2,831 LOC) into 7 logical modules (decoder, rig, vchan,
  sse, bookmarks, assets, mod)
- Extract background decode decision cascade into pure evaluate_bookmark()
  function with ChannelAction enum
- Relax actix-web pin from =4.4.1 to 4.4
- Replace VDES magic numbers with named constants

P3 — Low:
- Add doc comments to AisDecoder, VdesDecoder, RdsDecoder
- Add debug_assert on turbo decoder interleaver/deinterleaver lengths
- Add tracing info_span! to all 10 decoder block_in_place calls
- Optimize hot-path string cloning in remote_client spectrum loop

https://claude.ai/code/session_01Y3G65hrfsRRjwyBF2qbBmc
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 19:29:17 +02:00
ClaudeandStan Grams d512268526 [feat](trx-vdes): implement Turbo FEC, CRC-16, and link-layer parsing
Add the three missing VDES decoder components per ITU-R M.2092-1:

- turbo.rs: Turbo FEC decoder with dual 8-state RSC constituent
  encoders, BCJR/MAP iterative decoding (8 iterations), QPP
  interleaver, and rate-1/2 depuncturing
- crc.rs: CRC-16-CCITT validation (poly 0x1021, init 0xFFFF) for
  decoded link-layer frames
- link_layer.rs: Structured parsing of M.2092-1 link-layer frames
  (Messages 0-6) including station addressing, ASM identification,
  geographic bounding boxes, and ACK/NACK reporting

The main decode pipeline now attempts turbo decoding first with CRC
validation, falls back to Viterbi when turbo fails, and reports
crc_ok=true when either path validates. 27 tests covering all new
modules.

https://claude.ai/code/session_01SJSN7cv3zoL1xNcb8ex2zY
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 14:50:42 +02:00
ClaudeandStan Grams acd6ee93df [chore](trx-app): remove plugin system
Drop plugins.rs module and its sha2/hex/libc dependencies.
Plugin system was not part of the codebase — mark P0 and P3
plugin items as dropped in improvement areas doc.

https://claude.ai/code/session_01Gj1vEkP6GKVcVaMqzFW885
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 14:10:41 +02:00
ClaudeandStan Grams a69c5143e6 [refactor](trx-rs): resolve all improvement areas (P0-P3)
Addresses every item in docs/Improvement-Areas.md:

P0 - Plugin signing: new src/trx-app/src/plugins.rs with SHA-256 checksum
     manifest, filename allowlisting, API version compatibility checks,
     and cross-platform file permission validation.

P1 - Session store mutex poisoning: all .unwrap() calls on RwLock/Mutex in
     auth.rs replaced with .unwrap_or_else(|e| e.into_inner()) + warning logs.
   - TCP listener rate limiting: added ConnectionTracker with per-IP connection
     cap (10 concurrent connections per IP).
   - RigState refactoring: decoder fields grouped into DecoderConfig and
     DecoderResetSeqs sub-structs with #[serde(flatten)] for wire compat.
   - spawn_blocking timeout: satellite pass computation wrapped in 30s timeout.

P2 - Command handler macro: rig_command! macro generates 7 unit-struct command
     implementations, reducing ~200 lines of boilerplate.
   - Protocol versioning: added protocol_version field to ClientEnvelope and
     ClientResponse; improved unknown command error handling in parse_envelope.
   - Unsafe string: replaced from_utf8_unchecked with safe from_utf8().expect().
   - Dead code: removed 2 unnecessary annotations, documented remaining 4.

P3 - Tests: added 4 unit tests for history_store.rs (round-trip, expiry, etc).
   - FT-817 VFO: improved inference for ambiguous same-frequency case.
   - Configurator: implemented serial port detection via tokio_serial.
   - Plugin versioning: integrated into plugin manifest (api_version field).
   - Naming: documented as intentional semantic distinctions, not inconsistencies.

https://claude.ai/code/session_01Gj1vEkP6GKVcVaMqzFW885
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 14:10:41 +02:00
ClaudeandStan Grams 01d0b9efdd [fix](trx-server): fix compilation errors in audio, rig_task, and main
- Add lifetime parameter to lock_or_recover and fix missing .lock() call
- Replace undefined COMMAND_EXEC_TIMEOUT constant with local command_exec_timeout variable
- Add explicit type annotations to closure parameters in history snapshot methods
- Remove unused HostTrait import
- Fix non-existent machine_state/error_message fields on RigState in crash recovery

https://claude.ai/code/session_01HAkST2gLsYDXPom3282ABY
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 09:52:14 +02:00
ClaudeandStan Grams 6c08ff4776 [fix](trx-server): wire TimeoutsConfig into example config and fix test signatures
- Include TimeoutsConfig in --print-config example output
- Update run_listener() test call sites with new parameters

https://claude.ai/code/session_01P9G7QCWfiYbPVJ7cgiXznf
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 08:54:59 +02:00
ClaudeandStan Grams 16426548de [refactor](trx-rs): resolve all P1/P2 improvement areas
P1 (High Priority):
- Fix LIFO command batching in rig_task.rs (batch.pop→batch.remove(0))
- Add ±25% jitter to ExponentialBackoff to prevent thundering herd
- Add 10,000-entry capacity bounds to decoder history queues
- Add rig task crash detection with Error state broadcast
- Decompose FrontendRuntimeContext 50-field god-struct into 9 sub-structs
  (AudioContext, DecodeHistoryContext, HttpAuthConfig, HttpUiConfig,
   RigRoutingContext, OwnerInfo, VChanContext, SpectrumContext, PerRigAudioContext)
- Migrate std::sync::RwLock to tokio::sync::RwLock in background_decode.rs
- Extract find_input_device/find_output_device helpers from audio pipeline

P2 (Medium Priority):
- Introduce SoapySdrConfig builder struct (replaces 20+ positional params)
- Add define_command_mappings! macro for ClientCommand↔RigCommand mapping
- Replace silent lock poison recovery with lock_or_recover() warning logger
- Make timeouts configurable via RigTaskConfig/ListenerConfig and TOML
- Extract shared config types to trx-app/src/shared_config.rs

Documentation updated in CLAUDE.md, Architecture.md, Improvement-Areas.md.

https://claude.ai/code/session_01P9G7QCWfiYbPVJ7cgiXznf
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-29 08:54:59 +02:00
ClaudeandStan Grams 804b0d8846 [fix](trx-frontend-http): wire wxsat and lrpt decoders into bookmark toggle
The bookmark_decoder_state() and apply_scheduler_decoders() functions
only handled aprs, hf-aprs, ft8, ft4, ft2, and wspr decoder kinds.
The "wxsat" and "lrpt" entries from bookmark.decoders were silently
ignored, so toggling a bookmark with NOAA APT or Meteor LRPT ticked
never sent SetWxsatDecodeEnabled / SetLrptDecodeEnabled commands.

https://claude.ai/code/session_0198fyXkA3jooddgQyD9FpRZ
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 21:10:02 +01:00
ClaudeandStan Grams 9461ba2a85 [fix](trx-frontend-http): persist satellite pass preemption config
The SchedulerConfig struct was missing a `satellites` field, so the
frontend's satellite configuration (enabled flag, pretune seconds,
satellite entries) was silently dropped by serde on every PUT request,
causing the setting to reset immediately.

Added SatelliteConfig, SatelliteEntry structs and the `satellites`
field to SchedulerConfig.

https://claude.ai/code/session_01FMcYoHGy5K21maudnntueB
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 20:53:38 +01:00
ClaudeandStan Grams 4eac4458bf [fix](trx-frontend-http): add NOAA APT and Meteor LRPT to bookmark decode checkboxes
The Add Bookmark popup was missing NOAA APT (wxsat) and Meteor LRPT
decoder checkboxes. Added them to the HTML form, the read/write
functions, and the decoder toggle logic when applying bookmarks.

https://claude.ai/code/session_01FMcYoHGy5K21maudnntueB
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 20:53:38 +01:00
ClaudeandStan Grams 891141489c [refactor](trx-frontend-http): extract satellite scheduling UI into dedicated module
Move ~230 lines of satellite pass scheduling code from scheduler.js
into a new sat-scheduler.js plugin with cached DOM refs, createElement-
based rendering, and a clean bridge API. Refactor sat.js predictions
view to deduplicate row builders, extract countdown timer lifecycle
management, and cache all DOM references.

https://claude.ai/code/session_0144nUfHAKs7yRnYTsozNagw
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 20:38:58 +01:00
Claudeandsjg 3e3fdbcb30 [feat](trx-frontend-http): add satellite scheduler UI in web frontend
Add HTML, JS, and CSS for the satellite pass scheduling overlay in the
scheduler settings panel.  The satellite section is always visible
regardless of the base scheduler mode (Grayline/TimeSpan) since it
operates as a preemption overlay.

UI features:
- Enable/disable toggle for satellite pass preemption
- Configurable pre-tune seconds (time before AOS to start tuning)
- Satellite entry table with add/edit/remove (satellite name, NORAD ID,
  bookmark, min elevation, priority)
- Preset dropdown for common weather satellites (NOAA 15/18/19,
  Meteor-M2 3/4) that auto-fills name and NORAD ID
- Bookmark selector for each satellite (sets freq, mode, decoders)
- Live pass status badge showing active satellite from scheduler status
- Status card shows "[SAT: name]" label when satellite pass triggers
- Scheduler control row visible when satellites enabled (even with
  base mode disabled)

https://claude.ai/code/session_01WzWvhFVhEP9Fqn4u6pXs3T
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 20:21:29 +01:00
Claudeandsjg 8e700fb98a [feat](trx-frontend-http): optimize HTTP frontend performance
Server-side:
- Cache index_html() with OnceLock (avoids 3 string replacements per request)
- Pre-compress all static assets (JS/CSS/HTML) with gzip at startup, serve
  cached bytes with ETag + Cache-Control headers for browser caching
- Add If-None-Match / 304 Not Modified support for conditional GETs
- Serialize SSE state+meta in single serde pass via SnapshotWithMeta,
  eliminating the serialize → parse → flatten → re-serialize round-trip
- Add Cache-Control: immutable for favicon/logo (never change)

Client-side:
- Replace atob() + charCodeAt loop with direct base64 lookup-table decoder
  that writes to a reusable Int8Array (avoids UTF-16 string allocation)
- Spectrum bins now flow as Int8Array throughout the pipeline, reducing
  waterfall row memory from ~8 bytes/element to 1 byte/element
- Add isBinsArray() helper to support both Array and TypedArray in all
  spectrum/waterfall guard checks

https://claude.ai/code/session_01J3VCWZeEPsyFJiHjJRBREo
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 20:20:33 +01:00
Claudeandsjg 731410a7e6 [fix](trx-frontend-http): skip redundant DOM writes in render() hot path
The render() function runs on every SSE event (5-20×/sec) and was
unconditionally writing to decoder toggle buttons and About-tab
decoder status elements — 8 getElementById calls + 32 DOM property
writes per frame — even when values hadn't changed. This caused
unnecessary style recalculation overhead on every SSE frame,
contributing to spectrum stuttering.

Changes:
- Cache all 7 decoder toggle button elements at module init instead
  of calling getElementById on every render() call
- Track last-written enabled state per button; skip DOM writes when
  the value is unchanged (steady-state cost: 0 DOM writes per frame)
- Same pattern for 8 About-tab decoder status elements
- Gate updateSatLiveState className/textContent writes on value change

Net effect: eliminates ~50 unnecessary DOM operations per SSE frame
during normal operation (decoders rarely toggle).

https://claude.ai/code/session_01G6wuNCkckbHHsU7w5zCtW2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 19:09:15 +01:00
Claudeandsjg aacc7336d7 [fix](trx-frontend-http): optimize spectrum and waterfall rendering performance
Six hot-path optimizations that reduce per-frame CPU cost:

1. Waterfall color LUT: Pre-compute a 256-entry RGBA lookup table
   (bins are i8 = 256 possible values) instead of calling
   waterfallColorRgba() per-pixel with HSL→RGB math + Math.pow().
   Eliminates ~2000+ HSL conversions per frame across both waterfalls.

2. Noise floor O(N)→O(N log N): Replace .slice().sort() with an
   in-place quickselect algorithm for 15th-percentile estimation.
   For 1024 bins this is ~10× faster.

3. Reuse spectrum bin buffers: SSE handler and buildSpectrumRenderData
   now reuse pre-allocated arrays instead of creating new Array(N)
   and .map() allocations every frame. Reduces GC pressure.

4. Cache canvas dimensions: drawSpectrum and drawSpectrumWaterfall
   read cached CSS dimensions instead of querying clientWidth/
   clientHeight every frame (which forces layout recalculation).
   Dimensions refreshed on resize and layout changes.

5. Cache DOM references: getElementById calls for zoom indicator and
   minimap elements moved to module-level constants instead of
   querying the DOM on every drawSpectrum call.

6. Efficient array trimming: Peak hold pruning uses in-place splice
   from front instead of .filter() (which allocates a new array).
   Waterfall row trimming uses splice instead of repeated .shift().

https://claude.ai/code/session_01G6wuNCkckbHHsU7w5zCtW2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 19:00:58 +01:00
ClaudeandStan Grams 9920094008 [fix](trx-frontend-http): fix SAT prediction page degrading whole-page rendering
Three issues in the satellite predictions view caused page-wide
rendering performance degradation:

1. Unbounded DOM nodes: All satellite passes (200+ satellites × multiple
   passes = 500-1000 rows with 5 spans each) were rendered at once,
   creating thousands of DOM nodes that slowed style recalculation and
   layout across the entire page. Now caps at 50 visible rows with a
   "Show more" button.

2. No DOM cleanup on view switch: Prediction rows persisted in the DOM
   when navigating away from the predictions view or the SAT tab,
   bloating the page DOM indefinitely. Now clears prediction DOM when
   leaving the predictions view or switching decoder tabs.

3. Countdown timer never paused: The 1-second setInterval with
   querySelectorAll kept running even when the predictions view was
   hidden, wasting CPU on invisible DOM queries. Now only runs when
   predictions view is active, caches element references instead of
   querying the DOM each tick, and auto-pauses when the view is hidden.

Also caches prediction DOM element references at module init instead
of calling getElementById on every render invocation.

https://claude.ai/code/session_01G6wuNCkckbHHsU7w5zCtW2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 18:51:42 +01:00
Claudeandsjg 842ee6f076 [fix](trx-frontend-http): stop fetching /bookmarks on every SSE state update
applyRigList() was called on every SSE state update (since `remotes`
is always present in the payload), and it unconditionally called
bmFetch() which fires 2x GET /bookmarks (list + overlay). At the
default poll rate this generated ~20 bookmark fetches/second — visible
as constant GET /bookmarks traffic on each spectrum render cycle.

Now track the previous rig list + active rig as a key and only
re-fetch bookmarks (and re-init scheduler/background-decode) when
the rig list actually changes.

https://claude.ai/code/session_017g7VNMb6CChaiWrfzVBhbR
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 18:34:17 +01:00
ClaudeandStan Grams afaf19d2b4 [fix](trx-server): fix satellite pass computation degrading spectrum performance
Two issues introduced with wxsat/satellite support caused indirect
performance degradation on the spectrum rendering path:

1. spawn_tle_refresh_task() was called inside spawn_rig_audio_stack(),
   which runs per-rig. With N rigs this spawned N redundant TLE refresh
   tasks, each making 3 concurrent HTTP requests to CelesTrak and
   competing for write locks on the global TLE store. Moved to a single
   global call after the per-rig loop.

2. compute_upcoming_passes() (SGP4 propagation for 200+ satellites over
   24h = ~300K propagation steps) ran on every GetSatPasses request with
   no caching. Multiple client connections could trigger concurrent
   CPU-heavy computations, causing cache pollution and tokio runtime
   contention that indirectly slowed spectrum frame processing. Added a
   60-second server-side cache shared across all client connections.

https://claude.ai/code/session_017g7VNMb6CChaiWrfzVBhbR
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 18:25:40 +01:00
ClaudeandStan Grams 28769d01d0 [docs](trx-rs): document UI/UX design guidelines
Add docs/UX_Guidelines.md covering web frontend patterns (theming, responsive
design, accessibility, real-time data), REST API conventions, CLI interface,
configuration wizard, error handling, branding, security UX, and inferred
design principles.

https://claude.ai/code/session_01LC9Yp36ARX47bmKavNPTcB
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 14:09:24 +01:00
ClaudeandStan Grams 27117a8de5 [feat](trx-core): add periodic TLE refresh from CelesTrak
Fetch fresh weather satellite TLEs from CelesTrak on startup and then
once every 24 hours. The dynamic TLE store is checked first in
tle_for_satellite(), falling back to the existing hardcoded TLEs when
the fetch has not yet completed or fails.

- Add global TLE_STORE (RwLock<HashMap<norad_id, (line1, line2)>>)
- Add parse_tle_response() to parse 3-line TLE format
- Add refresh_tles_from_celestrak() async fetch + store update
- Add spawn_tle_refresh_task() for startup + daily refresh loop
- Refactor tle_for_satellite() into norad_id lookup + store check
- Spawn refresh task in trx-server alongside wxsat decoder tasks
- Add reqwest (rustls-tls) dependency to trx-core

https://claude.ai/code/session_01RB19i93dnemDYLcfrhyhqc
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 12:41:31 +01:00
ClaudeandStan Grams 929f1d3fab [fix](trx-server): remove unused pass_start_ms variable in wxsat decoder
The variable was assigned in six places but never read, producing
compiler warnings. The other `pass_start_ms` in `run_wxsat_image_task`
is unaffected and still used.

https://claude.ai/code/session_01SWe5x4CWy1q6BXtX4Gt3Qd
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 12:35:29 +01:00
ClaudeandStan Grams 560b6ec912 [feat](trx-rs): add weather satellite map overlay integration
Add SGP4-based geo-referencing for NOAA APT and Meteor LRPT decoded
satellite images, enabling them to be displayed as semi-transparent
overlays on the Leaflet map module with ground track polylines.

Changes:
- Add sgp4 crate dependency to trx-core for orbital propagation
- New trx-core/src/geo.rs module with TLE-based pass geo-referencing,
  ECI-to-geodetic conversion, and station-location fallback estimation
- Extend WxsatImage and LrptImage structs with geo_bounds and
  ground_track optional fields (backward compatible via serde defaults)
- Compute geo-bounds in finalize_wxsat_pass and finalize_lrpt_pass
  using satellite identity, pass timestamps, and station coordinates
- Add 'wxsat' source filter to the map module (off by default)
- Add L.imageOverlay rendering with popup and ground track polyline
- Add "Show on Map" buttons in wxsat plugin live/history views

https://claude.ai/code/session_01DUCfb9CjGoViwBrznpfWyt
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 12:24:36 +01:00
ClaudeandStan Grams c1b713a5b2 [feat](trx-rs): add GitHub workflow to sync docs/ to wiki
Introduces a GitHub Actions workflow that mirrors the docs/ directory
to the repository wiki on every push to main that touches docs/. Also
supports manual dispatch via workflow_dispatch.

https://claude.ai/code/session_01Hs4BtTczFdXaggpzaHFfen
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 11:29:57 +01:00
ClaudeandStan Grams 3ff848a715 [docs](trx-wxsat): add README with architecture and API documentation
https://claude.ai/code/session_01Cm1JpWMDZanjwKg3r2S3VR
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 11:23:33 +01:00
ClaudeandStan Grams 83a6b27130 [fix](trx-frontend-http): fix missing wxsat/lrpt history functions and lrpt_decode_enabled field
Remove calls to non-existent clear_wxsat_history and clear_lrpt_history
functions from the client-side clear endpoints. These image-based decoders
don't maintain client-side history unlike text decoders. The server-side
reset command (already sent) handles the cleanup. Also add missing
lrpt_decode_enabled field to the fallback RigSnapshot initializer.

https://claude.ai/code/session_019FkSMWpGR3XpWBvUghCybe
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 11:06:20 +01:00
ClaudeandStan Grams 9df71cf36c [docs](trx-rs): deep codebase review with updated architecture and improvement plan
Architecture.md: added detailed component notes covering rig_task internals,
audio pipeline, remote client dual-connection model, FrontendRuntimeContext
field groups, decoder implementation patterns, and FT-817 backend workarounds.

Improvement-Areas.md: added 10 new findings from deep review including LIFO
command batching, unbounded decoder history, missing jitter in backoff, rig
task crash recovery, SoapySdrRig constructor complexity, and protocol versioning.

CLAUDE.md: refreshed review observations with accurate LOC counts, prioritized
improvement items (P1/P2/P3), and new strengths identified.

https://claude.ai/code/session_011aiY4GfrmDUrpYVvEUGNGm
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:59:44 +01:00
ClaudeandStan Grams 384e1597f6 [style](trx-wxsat): apply cargo fmt formatting
https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
ClaudeandStan Grams 6d0c01c6c4 [feat](trx-frontend-http): add Live/History views to Weather Satellites panel
Replace flat image list with two switchable views:
- Live: decoder state cards (Idle/Listening), descriptions, latest image
- History: filterable table with columns for time, type, satellite,
  channels, lines, and download link. Supports text filter, type filter
  (All/APT/LRPT), and sort order (newest/oldest).

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
ClaudeandStan Grams 4d40c29e49 [refactor](trx-wxsat): unify image encoding to shared PNG module
Extract common image_enc module at crate root with encode_grayscale_png
and encode_rgb_png helpers. Both NOAA APT and Meteor-M LRPT now use PNG
as the output format through the shared encoder. Drop jpeg image feature
dependency.

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
ClaudeandStan Grams 1a3b815ed8 [feat](trx-wxsat): add Meteor-M LRPT decoder and Weather Satellites frontend panel
Restructure trx-wxsat into noaa/ (APT) and lrpt/ (Meteor-M LRPT) submodules
with shared crate base. Add QPSK demodulator, CCSDS CADU framer, MCU channel
assembler for LRPT. Wire LRPT through full stack (core types, protocol, server
decoder task, client). Add Weather Satellites sub-tab in Digital Modes with
toggle buttons for NOAA APT and Meteor LRPT, descriptions, and image history.

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
ClaudeandStan Grams d26ef6ca81 [feat](trx-wxsat): rename trx-noaa to trx-wxsat with full NOAA APT decode
Rename the crate from trx-noaa to trx-wxsat (weather satellite) across
the entire workspace. Add full NOAA satellite decode support:

- Telemetry frame parsing: extract 16-wedge calibration data from the
  128-line telemetry frames embedded in APT lines
- Radiometric calibration: piecewise-linear LUT built from wedges 1-8
  to correct pixel values against known reference levels
- Channel identification: detect AVHRR sensor channels (VIS, NIR, MIR,
  TIR) from wedge 9 values per APT sub-channel
- Satellite identification: heuristic NOAA-15/18/19 detection from
  channel A/B sensor pairings
- Histogram equalisation: per-channel contrast enhancement for improved
  image output
- WxsatImage now carries satellite name and channel labels in decoded
  message broadcasts

https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 10:36:34 +01:00
ClaudeandStan Grams a8b19227d5 [docs](trx-rs): codebase review - architecture docs, improvement plan, CLAUDE.md observations
Deep review of all 22 workspace crates (~52k LOC across 117 files).

- docs/architecture.md: system design, crate map, data flow, concurrency model
- docs/improvement-plan.md: 19 prioritized improvements (P0-P3)
- CLAUDE.md: updated crate layout (added missing crates), added review observations
  documenting strengths and areas for improvement

https://claude.ai/code/session_01CtmH5WraR6fjmt5Rx7ooEv
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 22:12:58 +01:00
ClaudeandStan Grams cf79df110a [fix](trx-frontend-http): map filter affects statistics, tooltips show receiver rig
Statistics panels (longest paths, strongest/weakest signals) now respect
all active map filters — source type, rig selector, band, search, and
history. Locator tooltips display which rig received each decoded frame.

https://claude.ai/code/session_01LT7zBnb2kQiYpeTuWNXHsT
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 22:01:43 +01:00
ClaudeandStan Grams 12726f3d2c [feat](trx-frontend-http): add R hotkey to round frequency up to next step
Remap retune from R to T, and add a new R hotkey that rounds the
current frequency forward to the next jog-step boundary. Both the
new hotkey and the remapped retune are documented in the F1 help
overlay.

https://claude.ai/code/session_017neG2jL9uXFSRpmhyS1EqG
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 21:52:28 +01:00
ClaudeandStan Grams 7c33af3d83 [feat](trx-frontend-http): add keyboard hotkeys for radio controls
Map R=retune, B=previous state, [/]=bandwidth ±10kHz, arrows=tune/center,
M=mode picker, Z=mono/stereo, N=noise blanker, Q=squelch toggle.
Document all shortcuts in the F1 help overlay.

https://claude.ai/code/session_01WDC889uQGW9XoPQqSZ6bVt
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 21:44:05 +01:00
ClaudeandStan Grams 71fe884ae6 [fix](trx-frontend-http): show all rigs data on map panel regardless of active rig
Remove rig_id filtering from dispatchDecodeMessage and dispatchDecodeBatch
so that decode data from all rigs (including remote/non-primary) flows into
the map. Also remove the rig_filter query param from decode history fetch
so all history is loaded. The existing map rig filter dropdown handles
visibility filtering via marker.__trxRigIds.

https://claude.ai/code/session_01GGvdXKdEbRBnJa2BjAQuVB
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 21:21:25 +01:00
Claudeandsjg 0905360ba5 [fix](trx-backend-soapysdr): remove noise/pilot dependency from WFM signal strength
The noise floor subtraction was over-aggressive: the bandwidth ratio
scaling between the 67 kHz baseband probe and the IQ domain amplified
the noise estimate excessively, causing weak stations to be subtracted
to nothing.  The pilot-referenced correction only worked for stereo
stations.

Strip the signal strength path back to what actually works universally:
mean IQ envelope power with asymmetric attack/decay smoothing.  This
always produces a reading for any FM signal — mono, stereo, with or
without RDS.

The baseband noise probe, CNR estimation, and pilot metrics remain in
the WfmStereoDecoder for their existing uses (RDS quality weighting,
CCI/ACI estimation) but no longer feed into the S-meter.

https://claude.ai/code/session_017URSDqSJ8TyZpDhV2vKZUe
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 20:50:06 +01:00
ClaudeandStan Grams 41fa9dc242 [feat](trx-backend-soapysdr): proper WFM signal strength algorithm
Replace the simple IQ power averaging with a proper WFM signal
strength measurement algorithm based on established RF engineering
practice:

1. Asymmetric attack/decay smoothing (τ_attack=2ms, τ_decay=300ms)
   per IARU Region 1 Technical Recommendation R.1 for professional
   S-meter behaviour.  Fast attack catches signal increases
   immediately; slow decay provides stable, readable meter movement.

2. Baseband noise floor estimation via a 67 kHz probe in the
   demodulated FM baseband.  FM demodulation noise follows an f²
   spectral shape, so energy above the useful baseband (audio +
   RDS ≤ 57 kHz) is dominated by channel noise and independent of
   program content.  Subtracting this noise estimate in the linear
   domain reveals the carrier-only power, preventing the meter from
   reading the noise floor on empty/weak channels.

3. Pilot-referenced quality correction.  The 19 kHz stereo pilot
   has a known fixed amplitude at the transmitter (±7.5 kHz
   deviation, 10% of ±75 kHz).  Near the FM threshold (~10 dB CNR)
   where noise dominates the IQ reading, the pilot tone power
   provides an independent quality-weighted correction.  The blend
   factor scales from 0.3 at low CNR down to 0 at high CNR where
   the raw IQ measurement is already accurate.

4. CNR estimation from the ratio of total baseband power to the
   above-band noise probe, enabling adaptive pilot correction and
   providing a signal quality metric for future use.

https://claude.ai/code/session_017URSDqSJ8TyZpDhV2vKZUe
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 20:33:09 +01:00
ClaudeandStan Grams 3ca3836702 [fix](trx-backend-soapysdr): measure WFM signal strength in IQ domain, not power domain
The previous carrier power IIR filtered |IQ|² (power), which only smoothed
temporal fluctuations but still integrated noise across the full 180 kHz WFM
channel bandwidth. This caused background noise to read ~-78 dBFS instead of
the expected ~-110 dBFS (~32 dB too high ≈ 10·log₁₀(180kHz/500Hz)).

Move the single-pole IIR lowpass to the IQ domain (filter I and Q separately
at ~500 Hz cutoff), then compute power from the filtered output. This rejects
out-of-band noise before the power measurement, so the meter reads true
carrier level rather than total wideband noise.

https://claude.ai/code/session_01W4WPMB2Lg3hgaY6opsk25f
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 18:28:35 +01:00
ClaudeandStan Grams 0ad3440a2d [fix](trx-backend-soapysdr): use narrow carrier IIR for WFM signal strength
Replace peak |IQ|² measurement with a per-sample single-pole IIR lowpass
on the instantaneous power (~500 Hz cutoff).  FM has constant envelope so
the IIR converges to the true carrier power A², rejecting wideband noise
that previously inflated the peak reading and masked actual signal level.

Other modes keep the existing peak + EMA approach.

https://claude.ai/code/session_01X6tedMVpjX3DEqLFDBR7FK
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 17:35:08 +01:00
ClaudeandStan Grams cf82c853cf [fix](trx-frontend-http): stabilize signal strength field width and BW overlay positioning
Increase sig-strength-display min-width to 7.5rem so the field no longer
resizes when the value switches between two-digit and three-digit numbers.

Reposition the fast BW overlay immediately when bandwidth changes arrive
via SSE, and force-display on bookmark apply so freq+bw render atomically
instead of the BW bars wiggling from a stale intermediate state.

https://claude.ai/code/session_01R2XBFEBL8CrsTx5inu25MA
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 17:18:45 +01:00
ClaudeandStan Grams 434a7f899d [fix](trx-frontend-http): stop SSE updates from resetting frequency input and causing redundant spectrum redraws
SSE status updates called applyLocalTunedFrequency with forceDisplay=true,
clearing the freqDirty flag on every update and overwriting user input mid-
typing. Remove forceDisplay from SSE path so the dirty flag is respected.

Skip applyLocalTunedFrequency entirely when frequency hasn't changed to
avoid redundant spectrum redraws and overlay repositioning on every SSE
frame. Only trigger scheduleSpectrumDraw when frequency actually changes.

Add blur and Escape handlers on frequency inputs to cleanly exit editing
mode when the user abandons input.

https://claude.ai/code/session_01H2VMATj29FPgR64t9YMdSR
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 17:02:57 +01:00
ClaudeandStan Grams ec0e41fe29 [refactor](trx-rds): remove Gardner TED to fix decoder freeze
The closed-loop Gardner Timing Error Detector was causing decoder
freezes under real-world conditions.  Remove all TED state and logic,
reverting to the simpler open-loop fixed clock_inc approach.  The
8-candidate parallel architecture already provides adequate timing
coverage via phase offsets without needing closed-loop tracking.

All other improvements (adaptive Costas bandwidth, syndrome-based OSD,
OSD(3/4), PI LLR accumulation) are retained.

https://claude.ai/code/session_01FsK5hZWGpAaaCpmWupN5AD
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:44:11 +01:00
ClaudeandStan Grams a387047464 [fix](trx-backend-soapysdr): widen RDS bandpass Q from 5.0 to 3.5 to reduce in-band distortion
The 8th-order (4×biquad) RDS bandpass at Q=5 per stage produced a
composite −3 dB bandwidth of ±2480 Hz, but the steep 8th-order roll-off
tapered the RDS signal edges (±1544 Hz at α=0.30) by −1.2 dB.  This
distorted the RRC matched filter's expected flat spectrum, causing ISI
and degrading soft-decision confidence — directly hurting PS/RT decode
on weak signals.

Q=3.5 widens the composite passband to ±3560 Hz, reducing band-edge
attenuation to −0.59 dB while still providing ≈−4 dB rejection at the
stereo difference signal edge (53 kHz) and steep 8th-order far-out
roll-off.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>

https://claude.ai/code/session_01Sw9esAuic8KHP1t8nZgvH2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:28:39 +01:00
ClaudeandStan Grams dd1af5aa94 [fix](trx-rds): revert RRC span to 10 chips to restore stopband rejection
The span-5 reduction passed synthetic tests because both the TX and RX
filters used the same truncated pulse shape (perfect matched filtering).
On real signals, the transmitter uses a full RRC pulse, and our truncated
RX filter couldn't match it — the weaker stopband rejection (~25% less
than pre-TED at α=0.30) allowed adjacent-channel interference through,
degrading soft confidence values and block decode rate, which caused
poor PS accumulation.

Span 10 at α=0.30 gives 50% better stopband rejection than the pre-TED
α=0.50/span=4 configuration, at the cost of 2048 vs 1024 FFT size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>

https://claude.ai/code/session_01Sw9esAuic8KHP1t8nZgvH2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:28:39 +01:00
ClaudeandStan Grams c92e53c3d3 [fix](trx-rds): restore pre-TED decode quality with tighter OSD, higher TED gate, and syndrome-based search
Three root causes for the post-TED decode quality regression:

1. OSD(4) at cost ceiling 0.60 produced excessive false positives at
   marginal SNR.  Tightened to OSD(2)/0.45 baseline, OSD(3)/0.50 only
   after 2+ successful groups.

2. Gardner TED activated after just 1 group (score >= 1), but a single
   false OSD match could trigger timing adjustments that injected jitter
   into soft values.  Raised lock gate to score >= 3 so the TED only
   engages after the candidate has proven itself on a real signal.

3. RRC filter span of 10 chips doubled FFT size to 2048 with negligible
   sensitivity gain over span 5 at α=0.30 (sidelobes beyond ±2.5 chips
   contribute <5% energy).  Reduced to span 5 → FFT 1024, matching
   pre-TED efficiency.

Additional optimizations (no quality impact):
- Syndrome-based OSD: replaces per-trial CRC recomputation with a single
  XOR per trial (CRC linearity), and sorts bit positions by ascending
  soft confidence so inner loops break early instead of continuing.
- Pre-allocated FFT scratch buffer: eliminates ~234 heap allocations/sec
  in the overlap-save convolution.
- PI_ACC_THRESHOLD reduced from 8 to 5 for faster acquisition while
  retaining reliable majority voting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>

https://claude.ai/code/session_01Sw9esAuic8KHP1t8nZgvH2
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 16:02:45 +01:00
ClaudeandStan Grams d864969742 [feat](trx-frontend-http): add clickable Sig Strength field between Wavelength and Frequency
Add a new "Sig Strength" display field in the freq row that shows
the measured signal strength. Clicking the field cycles through
three units: dBFS (default), dBf, and dBm. The selected unit is
persisted in localStorage.

https://claude.ai/code/session_01EvRV8UgsVtbrcH4t2hmFBF
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 13:37:47 +01:00
ClaudeandStan Grams 54ca86a93d [fix](trx-rds): stabilise Gardner TED to fix PI fluctuation and weak-signal regression
The Gardner TED (Tech 11) caused PI instability and worse weak-signal
pickup due to three issues:

1. Loop gains too aggressive: noise×noise error products at low SNR
   injected sub-chip jitter that degraded OSD soft confidence and PI
   LLR accumulation.  Reduced Kp from 4e-4→1.5e-4, Ki from 8e-8→2e-8
   (loop BW 0.11→0.053 Hz).

2. TED active during acquisition: before any group is decoded, the
   error signal is unreliable.  Now lock-gated (score >= 1) so the
   TED only engages after the first successful group decode, when
   timing is already close.  During acquisition, the 8-candidate
   architecture with fixed clocks provides adequate timing coverage.

3. Slow power estimate convergence: ted_power_est took ~420 ms to
   settle (0.999 alpha), causing the TED to over-steer during startup.
   Now uses 0.995 alpha (~84 ms convergence).

Additionally, when TED is gated off, the integrator decays toward zero
so stale corrections from a previous strong-signal period don't persist.

https://claude.ai/code/session_01KcVUcQQXrFyFA9NEjLhr9J
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 11:51:08 +01:00
ClaudeandStan Grams e44e616ab8 [fix](trx-backend-soapysdr): fix ACI/CCI always reading 0% in WFM
ACI: the hard limiter in channel.rs normalised IQ samples to unit
magnitude *before* the CMA equalizer, making the signal perfectly
constant-modulus so the CMA never adapted and tap deviation stayed
at zero.  Fix by moving the hard limiter inside process_iq (after
the CMA) and replacing the CMA-based metric with IQ envelope
coefficient of variation, computed on the raw samples.

CCI: the pilot coherence has a theoretical maximum of π/4 ≈ 0.785
(not 1.0), so coherence_penalty was always ~0.215 even for a clean
signal.  The Q/I ratio also depended on the arbitrary NCO-pilot
phase offset rather than actual interference.  Fix by normalising
coherence by its theoretical max and dropping the phase-dependent
Q/I ratio.  Gate CCI on pilot detection so mono signals read 0%.

https://claude.ai/code/session_01PUXWNMRGfrWYH56k2DLmen
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 11:38:03 +01:00
ClaudeandStan Grams d4d852456f [fix](trx-rds): stay locked between groups to prevent decoder freeze
After completing a group (Block D), the decoder dropped lock and
reverted to search mode which only uses hard CRC. On weak signals,
Block A frequently has bit errors that OSD could correct but hard
decode cannot, causing the decoder to freeze after 2-3 successful
groups. Stay locked with ExpectBlock::A so the next Block A benefits
from OSD soft decoding.

https://claude.ai/code/session_015Ds9dxpeyFimYHySBuzbFw
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 10:41:56 +01:00
ClaudeandStan Grams 4da4d8ec66 [feat](trx-rs): add CCI/ACI bars to WFM panel with RDS mitigation
Estimate Co-Channel Interference (CCI) from pilot tone quadrature
leakage and coherence degradation.  Estimate Adjacent Channel
Interference (ACI) from CMA equalizer tap deviation from identity.
Both metrics (0-100 scale) are surfaced through RigFilterState and
displayed as colour-coded bars in the WFM control panel.

The RDS decoder quality parameter is now adaptively penalised when
CCI/ACI levels are elevated, reducing block-error rate under
interference conditions.

https://claude.ai/code/session_016EKzep42RCvE4GxvvRaCwu
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 09:44:28 +01:00
Claudeandsjg 6b33550116 [fix](trx-rds): add staleness timeout to prevent decoder freeze
If the incumbent candidate has not produced a state update in 2 seconds,
clear its score advantage so any candidate can take over.  This prevents
the decoder from "freezing" on stale data when the incumbent's timing or
carrier tracking degrades — particularly important for dynamic PS where
the station rotates program service text.

Signed-off-by: Claude <noreply@anthropic.com>

https://claude.ai/code/session_0136sPdLUpYgvskrzbi2Epkv
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 08:46:29 +01:00
ClaudeandStan Grams 104629e373 [feat](trx-rds): push RDS decoding to 5 dB SNR
Fix Gardner TED loop structure bug (type-3 → type-2 PLL) and tune
gains for ζ=0.707 damping.  Add adaptive Costas loop bandwidth that
narrows from ~22 Hz to ~5.5 Hz once carrier is locked, reducing phase
noise at low SNR.  Narrow RRC matched filter (α=0.30, span=10 chips)
for ~0.6 dB noise BW gain.  Add OSD(4) for locked-mode blocks after
first successful group, and increase PI accumulation threshold to 8.

TED bug details: the original code used `clock_inc += correction`
which added the full integrator value at every chip, creating an
extra integration (type-3 loop) that is unconditionally unstable.
Fixed to `clock_inc = nominal + correction` (standard type-2 PLL).
Gains retuned: Kp=4e-4, Ki=8e-8 for ζ≈0.707 and loop BW≈0.11 Hz.

Signed-off-by: Claude <noreply@anthropic.com>

https://claude.ai/code/session_0136sPdLUpYgvskrzbi2Epkv
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 08:28:39 +01:00
ClaudeandStan Grams 2ba942f33b [refactor](trx-frontend-http): simplify auto-squelch to single-click action
Revert toggle approach back to a simple button: click sets squelch to
noise floor + 6 dB when spectrum data is available, or Off otherwise.

https://claude.ai/code/session_01TDQyrZiPKfWGATVWPsLmHT
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:49:18 +01:00
ClaudeandStan Grams 0e5410c0c5 [feat](trx-frontend-http): make auto-squelch a toggle, default to Off
The Auto button now toggles between Off and Auto states. Default is Off.
First click sets squelch to noise floor + 6 dB; second click resets to
Open (0%). Button shows active state with green highlight when engaged.

https://claude.ai/code/session_01TDQyrZiPKfWGATVWPsLmHT
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:49:18 +01:00
ClaudeandStan Grams 7edc8b7bfe [fix](trx-frontend-http): auto-squelch defaults to Off without spectrum data
When no spectrum data is available, the Auto button now sets squelch
to 0% (Off) instead of silently doing nothing.

https://claude.ai/code/session_01TDQyrZiPKfWGATVWPsLmHT
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:49:18 +01:00
ClaudeandStan Grams 2d8dfb1a3d [feat](trx-frontend-http): add auto-squelch button to Audio panel
Add an "Auto" button next to the SQL slider that sets the squelch
threshold to the current noise floor (estimated from spectrum bins)
plus a 6 dB margin. Uses the existing estimateNoiseFloorDb() heuristic.

https://claude.ai/code/session_01TDQyrZiPKfWGATVWPsLmHT
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:49:18 +01:00
ClaudeandStan Grams 5d6b9a4d94 [fix](trx-rds): reduce false decodes with OSD cost ceiling and PI consistency
Add OSD_MAX_FLIP_COST (0.45) to reject OSD corrections where the flipped
bits had high confidence — a strong false-decode indicator. Genuine errors
at 9-10 dB SNR have cost ≲0.3; noise matches cost 0.6-1.2.

Add PI consistency gate in process_group: reject groups whose Block A PI
differs from the candidate's established PI, preventing noise from
polluting accumulated PS/RT/PTYN text fields.

Raise PI_ACC_THRESHOLD from 2 to 3 for stronger PI voting.

Extend noise rejection test from 0.5s to 2s. Add 9 dB SNR sensitivity
test (all 16 tests pass).

https://claude.ai/code/session_01GYax4BQ9ZV9ZZfMjmmzgbh
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 07:20:22 +01:00
ClaudeandStan Grams 54b1f20ea4 [fix](trx-wspr): reduce false positives in WSPR decoder
The WSPR decoder was producing many false positive decodes due to
several overly permissive thresholds that allowed noise to reach the
Fano sequential decoder, which could then converge on random data:

- Raise normalized sync score threshold from 0.10 to 0.20 to reject
  noise candidates before attempting expensive Fano decoding
- Add minimum SNR gate (-20 dB) to skip candidates where the signal
  is indistinguishable from noise
- Return and check the Fano decoder's cumulative path metric, rejecting
  low-confidence decodes (metric < 20) that are likely noise artifacts
- Raise RMS threshold from 0.0005 to 0.005 to reject near-silent audio
- Add near-frequency deduplication to prevent the same signal decoded
  at slightly different (freq, dt) offsets from appearing multiple times
- Add noise-only regression test to verify no false positives on random
  input

https://claude.ai/code/session_01HTBoEsD1hp99TiYMSaHMVG
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 02:06:56 +01:00
ClaudeandStan Grams 06796342e7 [fix](trx-rds): apply RRC pulse shaping in test signal generator
The `chips_to_rds_signal` test helper was generating rectangular chip
pulses, but the receiver expects RRC-shaped transmit pulses so that
RRC(tx) × RRC(rx) = raised cosine with zero ISI. The rectangular
pulses caused ISI that drifted the symbol clock sampling point,
consistently skipping PS segment 2 in the end-to-end test.

Replace rectangular pulses with an impulse train convolved with the
same RRC taps used by the receiver. All 15 tests now pass including
`end_to_end_clean_signal_decodes_ps`.

https://claude.ai/code/session_01N2UcGaLDzYiM3gNrZ6kFBj
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-27 01:53:29 +01:00
ClaudeandStan Grams 8e6623b39e [fix](trx-rs): use per-message rig_id for map marker tagging
The map module was tagging all decode markers (APRS, AIS, VDES,
FT8/FT4/FT2/WSPR locators) with the global rig picker's active rig
instead of the actual source rig. This made the map's own rig filter
dropdown ineffective in multi-rig setups.

- Add rig_id field to all decode message structs (AisMessage,
  VdesMessage, AprsPacket, CwEvent, Ft8Message, WsprMessage)
- Set rig_id on messages in audio_client before broadcasting, using
  the actual rig connection identifier
- Update history collector to prefer message rig_id over the global
  active rig fallback
- Pass rig_id through plugin normalize functions (AIS, APRS, VDES,
  HF-APRS) so it reaches the map add functions
- Update all map marker functions (aprsMapAddStation, aisMapAddVessel,
  vdesMapAddPoint, mapAddLocator) to use the message's rig_id with
  fallback to the global picker for backward compatibility

https://claude.ai/code/session_015gC7axHk2jmp7HbFPdbivN
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 16:29:53 +01:00
ClaudeandStan Grams a63f27971d fix(trx-server): remove unused RigSdr import in rig_task.rs
Also run cargo fmt to fix formatting issues across trx-server,
trx-frontend-http, and trx-configurator.

https://claude.ai/code/session_01RsHUyVz2wjQjsEsxJo5owt
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 14:01:44 +01:00
ClaudeandStan Grams c8f33b8939 [refactor](trx-rs): remove shared-library plugin system
Drop the plugin loading infrastructure (libloading-based dynamic .so/.dylib/.dll
loading) from both trx-server and trx-client. The feature was unused and posed an
unnecessary security risk by executing arbitrary native code from disk.

Removed:
- src/trx-app/src/plugins.rs (plugin discovery, validation, FFI registration)
- examples/trx-plugin-example/ (cdylib example plugin)
- libloading dependency from trx-app
- load_backend_plugins / load_frontend_plugins calls from server and client
- Plugin documentation from README.md and CLAUDE.md

https://claude.ai/code/session_01DTEUpz3XPUeWmz74NeaFgb
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 12:44:56 +01:00
ClaudeandStan Grams 9692e31c8c [docs](trx-rs): update Improvement Areas wiki — mark resolved items
Update the Improvement Areas audit document to reflect all fixes
implemented across PRs #58, #59, and #60. 22 items now marked as
resolved; 5 remaining items reorganized by priority.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 11:42:25 +01:00
ClaudeandStan Grams 3e0169b91f [refactor](trx-backend-soapysdr): implement RigSdr trait for SoapySdrRig
Move 13 SDR-specific methods from impl RigCat into a new impl RigSdr
block. Add as_sdr()/as_sdr_ref() overrides returning Some(self) so the
SDR extension is accessible via the RigCat trait object.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
ClaudeandStan Grams 748d26f47d [refactor](trx-server): route SDR commands through RigCat::as_sdr()
Update all SDR command handlers in rig_task to access SDR methods via
ctx.rig.as_sdr() instead of calling them directly on RigCat. Query-only
SDR operations (filter_state, get_spectrum, get_vchan_rds) use
as_sdr_ref(). Non-SDR rigs now get proper not_supported errors.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
ClaudeandStan Grams 74749bc6de [refactor](trx-core): split RigCat into base trait + RigSdr extension
Extract 13 SDR-specific methods (set_center_freq, set_bandwidth,
set_sdr_gain/lna/agc/squelch/nb, set_wfm_*, filter_state, get_spectrum,
get_vchan_rds) into a new RigSdr trait. RigCat retains core CAT
operations and gains as_sdr()/as_sdr_ref() for optional SDR access.

Non-SDR backends no longer see SDR methods in their trait impl.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
ClaudeandStan Grams aa3ed81786 [fix](trx-server): replace all history mutex .expect() with poison recovery
Replace 25 .expect("X history mutex poisoned") calls in DecoderHistories
with .unwrap_or_else(|e| e.into_inner()) to gracefully recover from
poisoned locks instead of crashing the server.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 08:47:17 +01:00
ClaudeandStan Grams 337cb72974 [refactor](trx-server): use state data constructors for pub(crate) fields
Migrate ready_data_from_state and transmitting_data_from_state to use
the new ReadyStateData::new() and TransmittingStateData::new()
constructors instead of direct struct field initialization.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams 8ce2e4ed08 [refactor](trx-core): restrict state data fields to pub(crate) with accessors
Make ReadyStateData and TransmittingStateData fields pub(crate) to
prevent external mutation that could bypass state machine invariants.
Add constructors and getter methods for external consumers.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams 71c23f0895 [refactor](trx-frontend-http): pre-allocate spectrum encoding output
Replace format! with pre-allocated String::with_capacity for spectrum
frame encoding, reducing allocation overhead in the hot SSE path.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams adf65ae56d [fix](trx-frontend-http): warn when auth enabled but cookie_secure is false
Log a startup warning when HTTP auth is active but cookie_secure remains
false, alerting operators that session cookies will be sent unencrypted.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams 9b6c845fa8 [fix](trx-frontend-http): recover from poisoned locks and document ordering
Replace all .unwrap() on RwLock/Mutex acquisitions with
.unwrap_or_else(|e| e.into_inner()) to gracefully recover from poisoned
locks instead of panicking. Add lock ordering documentation to the
module header to prevent deadlocks.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams c5320ca2fb [refactor](trx-server): add AtomicUsize counter and recover from poisoned locks
Add an AtomicUsize total_count field to DecoderHistories, maintained by
record/prune/clear methods, so estimated_total_count() avoids 9 separate
mutex acquisitions. Also replace audio ring buffer .unwrap() calls with
.unwrap_or_else(|e| e.into_inner()) to recover from poisoned locks.

https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:29:55 +01:00
ClaudeandStan Grams 74eb755858 [chore](trx-rs): update Cargo.lock
https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-26 07:02:46 +01:00