feat(vfo): accent ADSP launcher when a client NR module is active (#3800)#3822
Conversation
…thersdr#3800) Enabling a client-side NR module (NR2 / NR4 / MNR / BNR / DFNR / RN2) left no cue anywhere on the main surface — the only way to tell one was on was to reopen the AetherDSP applet. The ADSP launcher is created non-checkable with a fixed kDspToggle stylesheet, so it never changed appearance. Add a green "active" accent (mirroring the radio-side toggles' :checked look) applied to the ADSP button whenever any of the six client NR modules is on. The state is folded into the button's accessibleName ("AetherDSP Settings (NR active)") so screen readers — and the agent automation bridge — can read it without a screenshot, and a stable objectName ("aetherDspBtn") keeps bridge targeting independent of that dynamic name. MainWindow wires the six AudioEngine *EnabledChanged signals (the modules are global engine state) to every slice's VfoWidget and syncs current state on slice creation. Closes the reporter's minimum ask; the top-right on-screen NR badge remains a follow-up. Closes aethersdr#3800 💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat
There was a problem hiding this comment.
Reviewed the diff against the surrounding code — this is clean, minimal, and correct. Thanks @jensenpat for the well-documented change and the bridge proof.
Verified against the codebase:
- All six
AudioEngineaccessors and*EnabledChangedsignals exist with the exact names wired here (AudioEngine.h:168-215,:502-509), so the OR and the sixconnect()s resolve correctly. - The connections use
was the receiver/context object, so they auto-drop when the slice'sVfoWidgetis destroyed — correct RAII, no dangling slot into a freed widget. Theif (!m_audio) return;guard inside the lambda covers engine teardown too. setAetherDspActive's early-return on the inactive baseline is safe: the"AetherDSP Settings"accessibleName is already set at construction (VfoWidget.cpp:1644), so the a11y baseline is never skipped. AndbuildTabContent()runs in the constructor (:1209), som_aetherDspBtnalways exists beforewireVfoWidgetcallssyncAetherDsp()— no ordering hazard where the button gets built after the state was already latched.rn2TxEnabledChanged(the TX mic pre-amp path,:506) is correctly excluded from the OR — this cue is about RX-side client NR only.
One thing worth noting (not a blocker): because these modules are global AudioEngine state, every open slice's ADSP button lights up green when any one is on, not just the slice whose applet enabled it. That's consistent with the modules being global and your PR body calls it out, so it's the right behavior — just flagging it for whoever merges, in case a future "per-slice NR" notion ever lands.
Conventions (AppSettings, RAII, C++20) all good; no new settings keys, no system-boundary error handling needed. Nice use of objectName to keep programmatic targeting stable independent of the now-dynamic accessibleName. LGTM.
🤖 aethersdr-agent · cost: $3.7223 · model: claude-opus-4-8
ten9876
left a comment
There was a problem hiding this comment.
Clean, correct UI/a11y fix. Verified: the OR-of-modules logic re-queries all six engine flags on every *EnabledChanged so toggling one off while another is on correctly stays active (the RN2 case); all six connect()s use w as receiver so they auto-drop on slice close (no dangling fire), with an m_audio null-guard; the trailing syncAetherDsp() applies current global state to a freshly-wired slice; setAetherDspActive is idempotent + null-guarded and swaps kDspToggle↔kDspToggleActive with no change to the click action. Good a11y (accessibleName '(NR active)') + stable objectName for the bridge. No TX/audio-behavior surface. Thanks @jensenpat.
Problem (#3800)
AetherSDR's client-side NR modules (NR2 / NR4 / MNR / BNR / DFNR / RN2) are global
AudioEnginestate that lives behind the ADSP launcher. That button is createdsetCheckable(false)with a fixedkDspTogglestylesheet, so it never changes appearance — enabling NR4 left no cue anywhere on the main surface. The radio-side toggles (NR/NRL/NRS/…) at least look pressed; the client modules had nothing.Fix
Accent the ADSP launcher whenever any of the six client NR modules is on:
kDspToggleActivestylesheet — the same green "active" look the radio-side toggles get via:checked— applied tom_aetherDspBtnwhen active, reverted tokDspTogglewhen all are off."AetherDSP Settings (NR active)"), so screen readers — and the agent automation bridge — can read the cue without a screenshot. A stableobjectName(aetherDspBtn) keeps programmatic targeting independent of that now-dynamic name.MainWindow::wireVfoWidget()connects the sixAudioEngine::*EnabledChangedsignals (the modules are global engine state) to every slice'sVfoWidgetand syncs the current state when a slice is created. Connections are bound to the widget so they drop when the slice closes.Minimal and self-contained:
VfoWidget.{h,cpp}+MainWindow_Wiring.cpp, 57 insertions, no behavior change to the launcher's click action. The "ideal" top-right on-screen NR badge from the issue remains a follow-up (it's a two-render-path SpectrumWidget change).How the agent automation bridge proved it
Built on a FLEX-8400M (KI6BCJ) and driven through the bridge (
tools/automation_probe.py) — RX-only, no TX:State read (
dumpTreeofaetherDspBtn.accessibleName):AetherDSP Settingsinvoke NR4 click→checkedAetherDSP Settings (NR active)invoke NR4 click→uncheckedAetherDSP SettingsOR-of-modules (RN2): RN2 on → active; NR2 toggled while RN2 still on → stays active; RN2 off → inactive. Confirms the indicator tracks the OR, not a single module.
Visual (
grab aetherDspBtn): active = green accent, inactive = default navy.kDspTogglekDspToggleActiveFinal state verified clean: indicator inactive (all client NR off),
transmitting == false.Closes #3800
💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat