Runtime-loaded styles.css; Linux Hamlib/QMX launcher wrapper; readable form-field text - #804
Merged
Merged
Conversation
…time Tauri embeds frontendDist into the compiled binary at build time (confirmed directly: editing the bundled dist/styles.css post-build and relaunching the same binary had zero effect on rendering), so any styling change previously needed a full rebuild to take effect -- painful with a Rust/Tauri build on constrained hardware, and heavy iteration on the stylesheet expected. New get_custom_css command (main.rs) reads a real file from the app's data directory (~/.local/share/FT8AF/styles.css on Linux) fresh on every launch, seeding it from the compiled-in default (public/styles.css, via include_str!) on first run so there's something to open and edit immediately. main.tsx now awaits this and injects the result as a <style> tag instead of a static Vite-bundled import, so editing that file and relaunching the app picks up changes with no rebuild at all -- same "resolved live, not baked in at compile time" idea already used for the Hamlib rig list. Verified end-to-end with a real screenshot test: launched the built binary, edited the on-disk styles.css to a loud, unmistakable color with no rebuild, relaunched the same binary, and confirmed the new color rendered. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…onflict
rig.rs's load_hamlib() does a bare dlopen("libhamlib.so.4") -- resolved
against whatever the dynamic linker finds first. On this machine that's the
distro-packaged libhamlib.so.4 (Hamlib 4.5.4), which has no QMX support at
all (confirmed: 0/283 rigs listed included QMX, and rig_init failed for
model 2057). A separately-built Hamlib 4.7.1 already exists at
/usr/local/lib/libhamlib.so.4 with QMX support (RIG_MODEL_QRPLABS_QMX
present) -- same soname as the system package, so whichever the linker
resolves first wins for any process that doesn't override the search path.
Not fixed by upgrading the system package: other software on this machine
(CQRLOG) depends on the distro Hamlib specifically. desktop/linux/run-ft8af.sh
instead sets LD_LIBRARY_PATH just for FT8AF's own process, so it
preferentially finds the newer build without touching anything system-wide.
Falls back to the system Hamlib (previous behavior) if no /usr/local build
exists.
Verified end-to-end through the wrapper: rig list jumps from 283 to 321
entries (QRPLabs QMX now included), and the app made a real, live CAT
connection to a physical QMX (kenwood_transaction traffic, frequency set,
54ms round-trip) -- confirmed via screenshot showing "rig: qmx" instead of
"no rig".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e widget background select and input elements set background: var(--panel) (dark), but WebKitGTK's native rendering doesn't fully respect that for <select> -- confirmed live, the rig/band dropdown and text fields render with a light background regardless, so the existing color: var(--text) (light, #d7e0e8 -- meant for dark panels) made every text box's contents nearly unreadable. Verified live via the new runtime styles.css: edited the on-disk file, relaunched with no rebuild, confirmed all Settings-tab inputs (Callsign, Grid, Display name, Radio) and both dropdowns (rig/band picker, Backend, Input/Output device) now render crisp black text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same root cause as the earlier unreadable-text fix (light --text color on a light native widget background), but a separate CSS property: caret-color was never set, so the blinking text cursor was effectively invisible. This explains a real, confusing user-facing symptom: with no visible caret, retyping a value without first clearing the field silently inserts new characters at the actual (but invisible) cursor position instead of replacing anything -- confirmed live, a garbled "AI5IIAI5IIAI555" ended up saved after a few real attempts to enter a callsign, matching exactly what duplicate-insertion-without-clearing produces. Root-caused by resetting the field to empty and typing a single character with the caret-color fix applied: cursor now renders as a clear, visible black line, confirmed both in a screenshot and by the user directly on the live display. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This reverts commit d42c4a8.
…ht native widget background" This reverts commit fb86646.
… field The earlier global select/input color override broke real keyboard input entirely (reverted in fb86646/28463d2 -- see project memory: project_ft8af_webkitgtk_input_bug.md). This is narrower and confirmed different: targets only the one Display name <input> via its placeholder text as a CSS attribute selector (no JSX change, no rebuild needed), not the shared select/input rule that also matches every native <select> dropdown. Verified live, twice, with a real keyboard: text renders white and readable, and typing into the field still works normally -- unlike every variant of the global rule change, which broke input every time it was tried. Callsign/Grid and the <select> dropdowns are untouched by this commit and still use native-default text color. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends the single-field-scoping approach validated in 9680d65 (Display name input) to every select that was unreadable against its native light background: Band, Rig control (Backend/Radio/Connection/Serial port/Baud), Audio (Input/Output device), and Developer waterfall FFT (Window function/FFT size/Averaging). Each gets its own id and a CSS rule targeting just that id -- the shared select/input rule stays untouched, since overriding color there is what broke real keyboard input earlier (see project_ft8af_webkitgtk_input_bug.md). Verified live with a real keyboard across two rebuild/retest cycles: closed-select display text and open dropdown-list text both render black and readable, Callsign/Grid typing still works normally, and none of the scoped selects show any of the input-blocking behavior the global rule change caused. Also commits main.rs's `--list-audio` debug helper (used earlier tonight to diagnose the audio device list, analogous to the existing --list-rigs) -- prints cpal's enumerated input/output devices and exits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New RX gain slider (0-100%, default 100%/unity) mirrors the existing TX gain pattern end to end: a lock-free atomic in the audio capture callback (AudioInput::set_gain(), read fresh per sample on the realtime thread, no locking), an EngineCommand::SetRxGain handler that persists to config and applies live without restarting capture, a set_rx_gain Tauri command, and an ipc.ts wrapper. Placement: next to the All/CQ/To me filter chips on the Decode screen, not buried in Settings -- real feedback was that this needs adjusting per-band (noise floor varies a lot band to band) while actively watching decodes, the same reasoning that will apply to relocating TX gain next to a future Tune button. Range settled at 0-100% (matching TX gain) after live testing: temporarily widened to 0-800% to confirm the control had real effect (confirmed: mute at 0%, a genuine clip warning near 800%, proportional movement of the raw dBFS meter in between), but adjustment past 100% wasn't practically useful -- the QMX's USB audio codec has no analog preamp stage (confirmed via amixer: only a capture on/off switch, no volume control), so there's no real gain headroom on the input side to exploit past unity. Settled on 0-100% for finer control resolution across the range that matters. Both TX and RX gain sliders now debounce their backend IPC call (~120ms after the last onChange) while keeping the displayed value instant -- dragging fires far more onChange events than needed, and that load stacking on the waterfall's own frequent canvas redraws correlates with a real WebKitGTK renderer crash observed live (the WebKitWebProcess child disappeared entirely, leaving a blank window, while the Rust backend kept running) -- not proven as the sole cause, but a safe mitigation regardless. Also clarified: the waterfall's lack of visible change across the gain range is by design (its own noise-floor-relative auto-leveling, stated in its own code comment), and the raw "audio dB" meter reading "silent" at low gain while decodes keep working is expected too -- it measures broadband RMS while the decoder/waterfall work in narrow FFT bins, and FT8 is designed to decode at very low SNR. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts: # desktop/src-tauri/src/engine.rs # desktop/src-tauri/src/main.rs # desktop/src/App.tsx
5 tasks
Author
|
@patrickrb the CI workflow runs on this PR (Desktop CI, Native FT8 tests, iOS CI, Android CI) are all sitting at |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #804 +/- ##
============================================
+ Coverage 42.14% 42.64% +0.49%
+ Complexity 228 226 -2
============================================
Files 270 267 -3
Lines 32257 32068 -189
Branches 3734 3664 -70
============================================
+ Hits 13596 13675 +79
+ Misses 18385 18132 -253
+ Partials 276 261 -15
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Select readability (styles.css): the per-id `color: #000` fixed the Linux
symptom but broke the other two platforms. WebKitGTK draws <select> as a
native GTK widget and ignores the author background, so --text sat on a
light control; WebView2 and WKWebView *do* honor the dark --panel
background, so black text there was black-on-#1a2129 -- and CI ships all
three. Opt selects out of native rendering with `appearance: none` so the
theme actually paints everywhere, redraw the arrow as a background image,
and pin both colors on the option list, which the platform draws outside
the page. Drops the dead #rig-radio-select selector along the way.
Stale on-disk stylesheet (main.rs): the seeded copy was frozen after the
first launch, so a later release that ships new markup would render it
unstyled with no in-app reset, and an interrupted first write left a
0-byte file that read back Ok("") and injected an empty <style> forever.
Stamp the seed with an FNV-1a hash of its own body: a file whose stamp
still matches is an untouched seed and is taken back when the compiled
default changes; an edited file, an unstamped one, or one we never wrote
is left alone. Empty or whitespace-only now re-seeds.
Unstyled fallback (main.tsx): with the bundled CSS import removed and
index.html linking nothing, the IPC read was the only source of styling
and the catch rendered anyway -- `npm run dev` in a plain browser, where
invoke rejects, came up as bare HTML. Fall back to the compiled-in copy.
RX gain: docs on SetRxGain, the engine field and set_gain still claimed
0.0-2.0 after the range was finalized at 0.0-1.0. Non-finite input now
falls back to the default instead of being clamped -- f32::clamp
propagates NaN, and the result is persisted where "NaN" parses back
cleanly, so one NaN would kill RX across restarts. The new full-scale
clamp in push_mono applies only above unity: at or below it, it could
only ever clip samples the device already delivered out of range, which
hard-clips the slightly-over-scale samples F32 backends (CoreAudio,
JACK/PipeWire) hand out on an otherwise-default install.
Linux launcher: LD_PRELOAD the one Hamlib rather than prepending
/usr/local/lib to LD_LIBRARY_PATH, which redirected every library FT8AF
resolves; and fall back to an installed ft8af on PATH so the script works
outside a source checkout, which is where its own docs send it.
Debounce: extracted to src/debounce.ts so it is testable, and cancelled
on unmount so a drag in flight cannot fire IPC after teardown.
Tests: resolve_styles/stamp round-trip and recovery cases, clamp_rx_gain
and the non-finite fallback for both gains, push_mono gain/clip/downmix
behavior, and the debounce module.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D7Z6xhcHB3XspStFrQ5puU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resubmission of #799 rebased onto current
dev(that PR's base had drifted ~1540 commits and gone into conflict). Same three desktop-app changes from real testing on Linux (aarch64), all verified live against a physical QRP Labs QMX transceiver:Runtime-loaded styles.css:
styles.csswasimported inmain.tsx, so Vite bundled it into a content-hashed file that Tauri embeds into the compiled binary at build time. Confirmed directly that editing the bundleddist/styles.cssafter a build and relaunching the same binary had zero effect. Now aget_custom_cssTauri command reads a real file from the app's data directory (~/.local/share/FT8AF/styles.csson Linux) fresh on every launch, seeding it from the compiled-in default on first run.main.tsxawaits this and injects the result as a<style>tag before rendering. Editing that file and relaunching now picks up changes with no rebuild at all.Linux launcher wrapper for a real Hamlib version conflict:
rig.rs'sload_hamlib()does a baredlopen("libhamlib.so.4"), resolved against whatever the dynamic linker finds first. On a system with both a distro-packaged Hamlib (no QMX support) and a separately-built newer one (with QMX support) sharing the same soname, the distro one wins by default — confirmed live: 0/283 rigs included QMX,rig_initfailed for the QMX model.desktop/linux/run-ft8af.shsetsLD_LIBRARY_PATHjust for FT8AF's own process, preferring a newer Hamlib build at/usr/local/libif one exists, with zero effect on any other software on the same machine that depends on the system Hamlib package. Falls back cleanly to the system Hamlib if no such build is present.Readable form-field text: on this WebKitGTK build, several
<input>/<select>fields (Display name, Band, Backend/Radio/Connection/Serial port/Baud, Audio Input/Output device, Developer waterfall FFT controls) rendered with light text against a native light background — unreadable. Fixed by scoping acoloroverride to each individual field (viaidor a placeholder-based attribute selector), not the sharedselect, inputrule. That distinction matters: overridingcoloron the shared rule was tried first and reverted after real-keyboard testing showed it broke text entry entirely across the app (confirmed withxevthat realKeyPressevents stopped reaching the window — a WebKitGTK quirk, not a data/logic bug, and not fixed by loading the same CSS the old static way either, ruling out the runtime-loading mechanism as the cause). Scoping the same color change to one field at a time was confirmed safe across every field it was applied to. Rebased ontodev's combined rig-backend/model selector and its removal of the standalone "serial" rig backend — the id-scoping now targets that current structure (the old separate "Radio" select and legacy serial-backend block no longer exist upstream, so they're gone from this fix too).Rebase notes (this resubmission)
runtime-styles-css, PR Runtime-loaded styles.css; Linux Hamlib/QMX launcher wrapper; readable form-field text #799) was cut fromdevon 2026-07-07 and had drifted ~1540 commits behind, producing merge conflicts. Re-merged onto currentorigin/dev(main.rs,engine.rs,App.tsxhad real conflicts —devhas since landed RX input gain, the WSJT-X UDP feature, OS-location support, and a rig-backend/model selector refactor; all preserved, resolved by hand, not overwritten).git diff origin/dev...HEADis functionally identical to the original PR's diff (same 8 files, ~same insertion count).npx tsc --noEmitpasses clean on the resolvedApp.tsx/ipc.ts. Rust toolchain is no longer installed on this machine (removed in an unrelated cleanup), so the Rust side is unverified locally — relying on CI plus the fact both Rust conflicts were non-overlapping additive hunks.Test plan
npm run tauri build) — original PR, prior to rebasestyles.cssto an unmistakable color with no rebuild, relaunched the same binary, confirmed the new color rendered--list-rigs: rig count went from 283 → 321 entries with QMX included, and confirmed a real, live CAT connection to a physical QMX transceiver through the wrapperxev-confirmedKeyPressdelivery, and manual typing into every affected field, across multiple rebuild/retest cyclesnpx tsc --noEmitclean after the rebase's conflict resolutionCloses #799.
🤖 Generated with Claude Code
https://claude.ai/code/session_01SaooA8HLsiETQ12Tbwwyzf