Include rig dial frequency and mode in WEFAX image filenames, matching
fldigi's approach of capturing tuning at save time. Images are saved to
~/.cache/trx-rs/wefax/. Server passes current rig state to the decoder
via set_tuning() before each processing block.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Phasing-only signals (no APT start tone) should not trigger image
decoding. Only APT start tones and signal-level variance detection
can start reception.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Per-entry caching in _ensureDecoderToggles prevents stale guard from
blocking re-scan. Direct syncWefaxToggle path ensures dataset.enabled
stays current for bookmark prefill.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
decoder.reset() now finalises and saves any partially-received image
before returning to Idle. The server emits the completion event so the
image appears in the frontend history and is persisted to disk.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add signal-level detection that monitors luminance variance to auto-start
receiving when tuning in mid-image (~3s of sustained modulated signal),
matching fldigi's "strong image signal" detection. Reduce APT sustain
to 1.0s (2 windows) matching fldigi. Emit initial "Idle — scanning"
state event so the frontend shows the decoder is processing audio.
Add tracing instrumentation for luminance stats and tone analysis.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Dynamic plugin scripts can execute before deferred app.js, causing
bookmarks.js to miss the onDecoderRegistryReady callback and never
build decoder checkboxes. Rebuild from the registry each time the
form opens so checkboxes always reflect the current registry state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Emit WefaxProgress events with a state label on each decoder state
transition (APT Start, Phasing, Receiving) so the frontend can display
the current decoder phase instead of just "listening for packets".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
APT start/stop signals are not audio-frequency tones — they are
black↔white transition rates in the FM-demodulated output (300, 675,
450 transitions/s). The Goertzel detector was running on the raw ~1900 Hz
carrier where no energy exists at those frequencies, so APT detection
never fired on real HF WEFAX signals.
Replace the Goertzel approach with transition-counting on demodulated
luminance (matching fldigi's decode_apt), and swap the processing order
so FM demodulation runs before APT detection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Two issues prevented bookmark decoder toggles from working:
1. bmPrefillFromStatus() did not prefill decoder checkboxes from the
current toggle button state, so bookmarks were saved with an empty
decoders array even when decoders were active.
2. The bookmark apply code fetched /status without the remote parameter,
comparing against the wrong rig's decoder state in multi-rig setups.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
When tuning into a WEFAX station after the APT start tone has already
passed, the decoder stayed in Idle forever. Add an idle_phasing detector
that continuously runs phasing detection on demodulated luminance while
in Idle state, allowing the decoder to lock onto ongoing transmissions
without requiring the 300/675 Hz start tone.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Normalize button styling between <a> and <button> elements by using
inline-flex with centered alignment instead of inline-block. Add
align-items to the container and box-sizing to the buttons.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
All decoder toggle endpoints (APRS, HF-APRS, CW, FT8, FT4, FT2, WSPR,
LRPT, WEFAX) read the enabled flag from the global default state watch
instead of the target rig's state. When controlling a non-active rig the
toggle reads the wrong rig's flag and sends the wrong enable/disable
value, causing the button to have no effect or invert the state.
Add resolve_rig_state() helper that looks up the per-rig watch via
context.rig_state_rx() and falls back to the global default, matching
the pattern already used by the /status endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
BackgroundDecodeManager.send_audio_cmd used the global active_rig_id()
to route virtual channel commands. During a rig switch, Remove commands
for the old rig's channels were sent to the new rig's audio pipeline,
leaving orphaned virtual channels on the previous rig's server.
Replace send_audio_cmd with send_audio_cmd_to_rig that takes an explicit
rig_id, derived from the channel's own rig_id field. Both Remove and
SubscribeBackground commands now reach the correct rig.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Replace per-sample circular-buffer processing with block-based linear
buffers in the FM discriminator and polyphase resampler. This eliminates
modular indexing in FIR inner loops, enabling compiler auto-vectorisation.
Also fix O(n²) drain pattern in the line slicer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Move default output directories from $XDG_DATA_HOME to $XDG_CACHE_HOME
so all runtime data lives under ~/.cache/trx-rs/ consistently.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Restructure the WEFAX tab to match the SAT/LRPT pattern with a
view switcher bar. Live view shows decoder description, live canvas,
and latest image card. History view adds a filterable, sortable table
of all decoded images with Clear All button.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
DIG mode provides the same SSB audio as USB, so WEFAX reception works
there. Added DIG to both the decoder registry active_modes and the
server-side mode gate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The WEFAX button had id="subtab-wefax" which duplicated the panel's id,
causing querySelector to match the button instead of the panel on click.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
- Fix WefaxProgress.line_data serialization: change from Vec<u8> (JSON
array) to base64-encoded String so the browser's atob() call works
- Set output_dir in server WefaxConfig to $XDG_CACHE_HOME/trx-rs/wefax
so decoded PNG images are persisted to disk
- Add /images/{filename} GET route in trx-frontend-http to serve saved
WEFAX PNGs with path traversal protection
- Capture live canvas as data URI on image completion for immediate
gallery thumbnail display without requiring the file serving route
https://claude.ai/code/session_01V1kLpgLPb8Q5wSv4UrcLbr
Signed-off-by: Claude <noreply@anthropic.com>
The wefax.js plugin defined wefaxToggleBtn but never attached a click
event listener, so clicking "Enable WEFAX" did nothing. Also switched
the clear button from raw fetch() to postPath() so it includes the
remote parameter in multi-rig setups.
https://claude.ai/code/session_01UJQpbecEBbphMZkSDKCiY6
Signed-off-by: Claude <noreply@anthropic.com>
Pure Rust WEFAX (Weather Facsimile) decoder supporting 60/90/120/240 LPM,
IOC 288 and 576, with automatic APT tone detection and phase alignment.
Core DSP pipeline:
- Polyphase rational resampler (48k→11025 Hz)
- FM discriminator (Hilbert FIR + instantaneous frequency)
- Goertzel tone detector (300/450/675 Hz APT tones)
- Phase alignment via cross-correlation on phasing signal
- Line slicer with linear interpolation pixel clock recovery
- Image assembler with PNG encoding
State machine: Idle→StartDetected→Phasing→Receiving→Stopping
Server integration:
- WefaxMessage/WefaxProgress in trx-core DecodedMessage
- DecoderConfig, DecoderResetSeqs, RigCommand wefax variants
- DECODER_REGISTRY entry in trx-protocol
- DecoderHistories/DecoderLoggers wefax support
- run_wefax_decoder() async task in trx-server audio.rs
- History persistence in pickledb store
Frontend integration:
- wefax.js plugin with live canvas rendering and gallery
- HTML sub-tab with canvas, gallery, toggle/clear controls
- SSE dispatch for wefax/wefax_progress events
- Decode history worker and restore support
- Toggle/clear API endpoints
19 unit tests covering resampler, FM discriminator, tone detection,
phasing, line slicing, image encoding, and decoder state machine.
https://claude.ai/code/session_019eyxgx3LuhcFZ7T5tr2Trm
Signed-off-by: Claude <noreply@anthropic.com>
Expand §7.5 from three bullet points into a comprehensive frontend
integration specification covering: Rust asset pipeline (status.rs,
assets.rs, decoder.rs, api/mod.rs), HTML sub-tab/canvas/gallery markup,
plugin loading registration, SSE decode event dispatch, full wefax.js
plugin with live canvas line-painting and gallery thumbnails, image
serving route, and decode history worker integration. Add Phase 3b to
implementation roadmap for the frontend work items.
https://claude.ai/code/session_01CbnUSjFGUzddvzwmddn5V6
Signed-off-by: Claude <noreply@anthropic.com>
After fetching /decoders, hide sub-tab buttons, panels, overview
descriptions, about-status rows, and settings clear-history buttons
for decoders the server doesn't advertise. This makes feature-gated
decoders like FT2 fully invisible in the UI when disabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The inline row editor rendered extra channels as read-only text and
never saved changes to bookmark_ids. Add a dropdown + chip UI matching
the form modal pattern so users can add/remove extra channels inline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
FT2 decoder implementation, protocol constants, server decoder tasks,
background decode, and registry entry are now conditional on the ft2
feature. Lightweight types (enum variants, commands, state fields) remain
unconditional to avoid cascading cfg noise in macros and serde.
Enable with: cargo build -p trx-server --features ft2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
sat.js was only in the 'map' plugin group but the SAT subtab lives
under digital-modes. History/Predictions buttons had no click handlers
until the map tab was visited. The loaded Set prevents double-loading.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Wrap unit labels (dBm, dBf, dBFS, S, dB) in a .sig-unit span styled
with the system monospace stack, keeping numeric values in DSEG14.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The Download button is an <a> tag which inherits default link styles
(underline, mismatched font/sizing). Added text-decoration, display,
font-family, and line-height to normalize both <a> and <button> elements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The @font-face unicode-range only included digits and punctuation, so
letter characters in RDS station names fell back to generic monospace.
Expanded to U+0020-007E (full printable ASCII) matching all glyphs in
the font.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Function was defined in map-core.js but not exported, causing a TypeError
when app.js called window.trx.map.reverseGeocodeLocation().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Same issue as Leaflet — content blockers block the jsdelivr CDN request,
causing the seven-segment font to fail loading and fall back to monospace.
Also replace preload-to-stylesheet swap with media="print" onload swap
for themes.css and leaflet.css to eliminate Safari preload warnings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The .rds-ps class was missing font-family after JS refactoring, causing it
to inherit the generic monospace stack from .rds-value instead of using the
seven-segment DSEG14 Classic font.
Fixes#141
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Add markDecodeMapSyncPending, decodeHistoryMapRenderingDeferred,
decodeHistoryReplayActive, decodeMapSyncPending, updateDocumentTitle,
activeChannelRds, _activeTab, and locationSubtitle to window.trx so
map-core.js can access them via the T alias.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
navigateToTab now calls loadPluginsForTab to ensure map-core.js is
injected on initial page load from /map URL. map-core.js auto-inits the
map if the map tab is already visible when the module loads.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
Bundle Leaflet JS, CSS, and marker/layer images as embedded assets served
under /vendor/ instead of loading from unpkg.com, which content blockers
(e.g. Safari) prevent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
The lazy plugin loader (25c5940) deferred scheduler.js to the 'settings'
tab, but initSettingsUI() runs at app boot before the user clicks any tab.
This meant initScheduler was undefined at boot, so the scheduler-control-row
on the main tab never showed and status polling never started.
Two fixes:
1. Add 'settings' to the eagerly-loaded plugin list so scheduler.js and
vchan.js load at startup alongside digital-modes and bookmarks.
2. Add auto-init at the end of scheduler.js: if authRole is already set
when the script executes (late/lazy load), it self-initializes without
waiting for initSettingsUI(). This makes it resilient to any load order.
https://claude.ai/code/session_01YNwgQGjCdLjVMcatVy3uQi
Signed-off-by: Claude <noreply@anthropic.com>
The lazy plugin loader introduced in 25c5940 incorrectly mapped
scheduler.js to the 'recorder' tab. The scheduler UI lives under
Settings → Scheduler, so the plugin must load with the 'settings' tab.
This caused the scheduler controls to be invisible and non-functional.
https://claude.ai/code/session_01NuatkhpFU7JCRnAbNUavPk
Signed-off-by: Claude <noreply@anthropic.com>
escapeMapHtml was defined inside the map-core.js IIFE, making it
inaccessible to app.js and plugin files (aprs, ais, vdes, cw, hf-aprs)
that call it from global scope, causing ReferenceError at runtime.
Move the function definition to app.js (global scope), export it via
window.trx, and destructure it in map-core.js like other shared utils.
https://claude.ai/code/session_01RhL8cCcszaguKqoWn5XUxL
Signed-off-by: Claude <noreply@anthropic.com>
Replace hardcoded rgba(15, 23, 42, ...) backgrounds and #b31217/#ff7b7b
colors with color-mix() using CSS custom properties (--card-bg, --bg,
--text, --accent-red). This ensures RDS overlays, decoder bar overlays
(APRS, AIS, VDES, FT8, CW), header-main, and tab containers all
respect the selected color scheme and light/dark theme.
https://claude.ai/code/session_01L8XeLh7iHnX3LGLbqswLPu
Signed-off-by: Claude <noreply@anthropic.com>
Extract map-core.js (3,483 lines) and screenshot.js (261 lines) from
the monolithic app.js, reducing it by ~30% (11,967 → 8,427 lines).
Modules communicate via a window.trx shared namespace with getter/setter-
backed state proxying. Map and statistics code lazy-loads on first tab
activation; screenshot code lazy-loads on first "S" keypress. All cross-
module calls use optional chaining for safe access before modules load.
Adds Rust infrastructure (include_str, gz_cache, Actix routes) for
serving the new JS assets.
https://claude.ai/code/session_01HgW8UpscRRA3CgSLqQDzdp
Signed-off-by: Claude <noreply@anthropic.com>
The auth-badge element was wrapped inside <template id="tmpl-about">,
making it invisible to document.getElementById() at page load.
updateAuthUI() accessed badge.style without a null check, throwing a
TypeError that halted app initialization before connect() was called.
Move auth-badge outside the template so it is always in the live DOM,
and add defensive null guards on badge/badgeRole access.
https://claude.ai/code/session_01Km7uxYUzehpYBdYqncnt4n
Signed-off-by: Claude <noreply@anthropic.com>
CSS: reduce backdrop-filter to modals only, add contain/content-visibility
for inactive tabs, optimize transitions to background-color, pre-compute
color-mix results, add container queries, split themes to lazy-loaded file.
JS: cache DOM refs in render path, add field-level diffing for SSE updates,
replace innerHTML with replaceChildren() in hot paths, add WebGL colour
cache invalidation on theme switch.
HTML: add defer to scripts, lazy-load plugin scripts on tab activation,
SVG sprite sheet for tab icons, template elements for deferred tab content,
improve aria-live/keyboard nav/colour contrast accessibility.
Server: upgrade Cache-Control to immutable, add Brotli compression alongside
gzip with Accept-Encoding negotiation.
Implements all items from docs/frontend_improvements.md except app.js ES
module split (P1, requires major refactor) and Web Worker migration (P3).
https://claude.ai/code/session_015rQNMGvusj5jY66MPUgYqt
Signed-off-by: Claude <noreply@anthropic.com>
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>