feat(bridge): get dsp, streams resync, selectRow/window verbs, TX-guard fix + a11y (#3856, #3918)#3920
Conversation
…ck-state (aethersdr#3856) Add a few automation-bridge verbs and a11y fixes that close gaps the dogfooding loop surfaced: - get dsp — the client-side AetherDSP model that was missing. `get slice` reports the radio-side nr/nb/anf; this reports the six client-side AudioEngine modules (NR2/NR4/MNR/DFNR/RN2/BNR): the active method, per-module enabled/available (compile-time backend gating), and their tuning values (engine getters read on the audio thread; NR2/NR4/DFNR-beta slider params merged from AppSettings). - streams resync — re-subscribe (`sub pan all`) to force the radio to re-dump every allocated display object, refreshing the Layer-B inventory to the radio's present-tense set. Closes the gap where a waterfall lingers as a radio resource but no longer emits UDP (Layer A can't see it) and the client already purged its view (Layer B looked clean). Async: re-poll `streams radio` after it settles. Does not clear the maps first, so firmware that doesn't re-dump leaves the inventory intact rather than wiped. - DSP method buttons are now named — setObjectName/setAccessibleName on each NR2…BNR selector. A checkable button reported value as "checked"/ "unchecked" with no label, so a driver (or assistive tech) couldn't tell which method was active without a screenshot. Satisfies check_a11y.py. - dumpTree now surfaces a checkable button's text + a checked bool, so the identity and state are both readable from the tree alone. Built clean (RelWithDebInfo). Live bridge proof deferred to an exclusive radio/desktop turn (another instance was holding the radio with a run queued); verify with: `get dsp`, `dumpTree` (DSP buttons carry objectName/text/checked), `streams resync` then `streams radio`. 💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…uard tune fix (aethersdr#3918) Three more automation-bridge improvements surfaced while dogfooding the net-scheduler flow: - TX-guard false-positive fixed: isTransmitControl()'s button-name fallback denied any button whose tokens included "tune"/"atu"/"vox", which blocked the RX-only "Tune Now" button (net/spot retune — no keying). The QAction path had already narrowed to mox/ptt/transmit/cwx; the button path hadn't. Aligned them. The genuine keying TUNE/ATU buttons (TxApplet, AtuPreTuneDialog) all carry the authoritative markTxKeying() marker, which the positive check still honors, so no real protection is lost. - invoke <view> selectRow <n> — select a whole row in a QTableWidget / QTreeWidget / QListWidget (current index + full-row selection) so a dialog's row-scoped buttons (Tune/Edit/Remove/Disable) become drivable. Echoes selectedRow + selectedRowText; bounds-checked. - window <maximize|restore|minimize|fullscreen> [target] + dumpTree now carries windowState for top-level windows. resize only ever set explicit geometry, so an un-maximize was unverifiable; now both the action and the assertion exist. Proven live (headless, offscreen, no radio) on a clean build: - window maximize/restore/fullscreen → response + dumpTree windowState both flip maximized/normal/fullscreen; bad action → error. - selectRow on the Net Scheduler "Scheduled nets" table → ok, selectedRow:0, selectedRowText:"✓"; out-of-range → clean error. - "Tune Now" (RX-only) invoked WITHOUT AETHER_AUTOMATION_ALLOW_TX → ok (deferred), no longer blocked. Test net entry added + removed, config left empty. Also proved the earlier batch live: get dsp (active/methods/tuning) and the six DSP buttons now carrying objectName=dspMethodBtn*/accessibleName/text/ checked in dumpTree. 💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Thanks @jensenpat — this is a well-structured batch and the per-verb proof table plus the narrow, marker-first TX-guard reasoning make it easy to follow. CI is green across build/win/mac/CodeQL/a11y. A couple of things, one worth fixing before merge.
1. get dsp NR4 tuning reports the wrong defaults (data-accuracy bug)
The NR4 fallback values in doGet don't match the authoritative defaults the engine/applet actually use, so on a fresh config (key unset) get dsp reports values the engine isn't running — which defeats the "assert state without a screenshot" goal:
{QStringLiteral("reductionDb"), s.value("NR4ReductionAmount", "100").toFloat()}, // engine default "10.0"
{QStringLiteral("maskingDepth"), s.value("NR4MaskingDepth", "50").toFloat()}, // engine default "0.50"
{QStringLiteral("suppression"), s.value("NR4SuppressionStrength", "50").toFloat()},// engine default "0.50"Compare AudioEngine.cpp:5384‑5390 (and AetherDspWidget.cpp:1078/1090/1094), which use "10.0", "0.50", "0.50". maskingDepth/suppression are stored as 0..1 fractions, so the "50" defaults are also off by ~100×, and when the user has set them the snapshot returns the raw 0.50-style fraction — so the field is internally inconsistent (fraction when set, 50 when unset). The NR2 defaults (1.50/0.85/0.20/2/0) all match correctly; only these three NR4 ones drift. Suggest copying the exact default strings from AudioEngine.cpp so unset configs report the true engine state.
2. Minor — DFNR available vs the selector-button guard
dspEngineSnapshot gates DFNR's available on HAVE_DFNR, but unlike MNR/NR4/BNR the AetherDsp selector button isn't dimmed by HAVE_DFNR in AetherDspWidget.cpp — so the "same guards the selector buttons use" framing doesn't quite hold for DFNR. Reporting available:false when HAVE_DFNR is undefined is arguably more correct, so no change needed, just flagging the small divergence from the PR description.
What I checked and liked
- TX-guard narrowing is sound.
isTransmitControl()checks theaetherTxKeyingmarker first, and every genuine keying TUNE/ATU button (TxApplet.cpp:235/259,AtuPreTuneDialog.cpp:303/341/348) carriesmarkTxKeying(), so droppingtune/atu/voxfrom the name fallback loses no real protection — it only stops false-blocking RX-only "Tune Now". The fallback remains a logged warning prompting future marks. - Null-safety is fine.
doStreamsguardsm_radioModelat the top beforeresyncDisplayInventory();get dspguardsm_audioEngine. topLevelWindowForTargetrefactor cleanly dedups doResize/doWindow; static→staticresolveWidgetcall is valid.selectRowis bounds-checked and sets both current-index and full-row selection; the snapshot's threading mirrors the existingaudioSnapshotOnObjectThreadpattern (engine getters on the audio thread, AppSettings merged on the bridge thread).
Just the NR4 defaults in (1) are worth fixing; the rest looks good.
🤖 aethersdr-agent · cost: $6.4494 · model: claude-opus-4-8
ten9876
left a comment
There was a problem hiding this comment.
Approved. High-effort review (8 finders + verify): the safety-critical TX-guard change is verified sound — markTxKeying() covers all keying controls (TUNE/ATU/MOX/CWX), VOX is arming-only, and the change correctly stops false-positive blocking of the RX-only Tune buttons. selectRow validates its index + bounds, bridge handlers are GUI-thread synchronous (no races), and get dsp's mutually-exclusive module reporting is fine. Only follow-up is documenting the four new verbs in docs/automation-bridge.md (filing an issue). CI green.
Resolves the AutomationServer.cpp conflict from aethersdr#3920 (bridge verbs): the `get <model>` help string now lists the union of both PRs' models — `dsp` (from aethersdr#3920, on main) and `kiwi` (this branch). Both get-model handlers are present and unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ngine bnr* API (#3931) ## 🔴 `main` does not compile `AutomationServer.cpp::dspEngineSnapshot()` (added in #3920) calls `AudioEngine::bnrEnabled()` / `bnrIntensity()` / `bnrAddress()` / `bnrConnected()`, but **#3902 removed those** when it replaced the containerized Maxine-NIM BNR with the in-process AFX denoiser (`AudioEngine` now exposes `nvAfxEnabled()` etc.). ``` AutomationServer.cpp(1235): error C2039: 'bnrEnabled': is not a member of 'AudioEngine' AutomationServer.cpp(1259): error C2039: 'bnrIntensity' ... AutomationServer.cpp(1260): error C2039: 'bnrAddress' ... AutomationServer.cpp(1261): error C2039: 'bnrConnected' ... ``` These are platform-independent member errors, so **every** build is red. #3920 was CI-green against a pre-#3902 base, so the semantic conflict slipped through on merge (no re-CI of the merged result). ## Fix - `a->bnrEnabled()` → `a->nvAfxEnabled()` (always present, even on non-AFX builds), and the availability guard `HAVE_BNR` → `HAVE_NVIDIA_AFX` (`HAVE_BNR` is no longer defined anywhere). - The in-process AFX has no container, so `address`/`connected` are dropped from the `bnr` tuning; report the persisted `NvidiaBnrSettings::intensity()` instead. One file, no behavior change beyond the snapshot's BNR fields. All other `bnr*` references in the tree were checked — these four were the only stale ones. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…aethersdr#3928) PR aethersdr#3920 added four verbs to the header comments but never to the public reference. A full audit of AutomationServer.{cpp,h} against docs/automation-bridge.md found the doc was missing far more than those four: ~20 verbs and several sub-actions had no public documentation at all. Audit method: extracted the complete verb surface from every handler (handleLine dispatch, doInvoke actions, doGet models, and all do* sub-action handlers), cross-checked it against the doc, and verified completeness against the raw `cmd ==`/`model ==`/`action ==` token lists. Every cmd token, get model, and invoke action is now documented (self-checked). Added/updated: - New "Verb index" — one categorized quick-reference table of the complete catalog, each row linking to its section. - New sections: window, resize, menu, tune, slice (full: add/remove/select/tx/ txant/rxant/rxsource), txwaterfall, floors, whoami, mark, log, record, station, and a "Transmit verbs (gated)" group (key, cwx, txtest, atu, testtone) with the ALLOW_TX rail + force-unkey watchdog spelled out. - invoke: documented selectRow and submit (and submit-vs-setText). - get: added dsp (with the corrected active/methods/tuning shape) and meters; noted receiveSync is an alias of sync. - streams: added resync (alias refresh); framed the ~500ms re-poll as best-effort. - dumpTree: documented windowState, checkable-button text/checked, sliceId, and the per-node noiseFloorDbm/displayFloorDbm. - TX-safety callout: corrected the button name-fallback to the narrowed mox/ptt/transmit/cwx set (tune/atu/vox were dropped in aethersdr#3918). Docs-only; no code change. Every documented request/response shape was spot-checked live against a running bridge (offscreen, FLEX-8400M). Fixes aethersdr#3928. 💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…#3928) (#3929) ## What Issue #3928 asked to document the four verbs PR #3920 added to the header but not to the public reference. This goes further per the request: a **full audit of every automation-bridge verb**, so `docs/automation-bridge.md` is a complete, categorized reference an agent can drive from. The audit found the doc was missing **far more than four** — ~20 verbs and several sub-actions had no public documentation. ## Audit method Extracted the complete verb surface from the code (the `handleLine` dispatch, every `doInvoke` action, every `doGet` model, and all `do*` sub-action handlers), cross-checked it against the doc, and verified completeness against the raw `cmd ==` / `model ==` / `action ==` token lists. **Result: every cmd token, get model, and invoke action is now documented** (self-checked — zero missing). ## What was undocumented (now fixed) | Area | Was missing | |---|---| | **New sections** | `window`, `resize`, `menu`, `tune`, `slice` (full: add/remove/select/tx/txant/rxant/rxsource), `txwaterfall`, `floors`, `whoami`, `mark`, `log`, `record`, `station` | | **Transmit (gated) group** | `key`, `cwx`, `txtest`, `atu`, `testtone` — with the `AETHER_AUTOMATION_ALLOW_TX` rail + force-unkey watchdog spelled out | | **`invoke` actions** | `selectRow`, `submit` (+ submit-vs-setText) | | **`get` models** | `dsp` (corrected `active`/`methods`/`tuning` shape — no top-level `enabled`), `meters`; noted `receiveSync` aliases `sync` | | **`streams`** | `resync` (alias `refresh`); ~500 ms re-poll framed as best-effort, not a contract | | **`dumpTree` node fields** | `windowState`, checkable-button `text`/`checked`, `sliceId`, per-node `noiseFloorDbm`/`displayFloorDbm` | | **TX-safety callout** | corrected the button name-fallback to the narrowed `mox/ptt/transmit/cwx` set (`tune`/`atu`/`vox` were dropped in #3918 so "Tune Now" isn't false-blocked) | Plus a new **Verb index** at the top of `## Verbs`: one categorized quick-reference table of the complete catalog, each row linking to its section. ## Proof — spot-checked live Docs-only (no code change), but every documented request/response shape was run against a live bridge (`QT_QPA_PLATFORM=offscreen`, FLEX-8400M) and the JSON matched, e.g.: ``` get dsp active → {"model":"dsp","ok":true,"property":"active","value":"none"} window fullscreen → {"action":"fullscreen","ok":true,"windowState":"fullscreen", …} streams resync → {"ok":true,"scope":"radio","resync":"requested","hint":"re-poll 'streams radio' after ~500ms …"} log set aether.dsp on → {"enabled":true,"id":"aether.dsp","ok":true} floors → {"ok":true,"floors":[{"panIndex":0,"noiseFloorDbm":-99.68,"displayFloorDbm":-99.17,"visible":true}]} invoke … selectRow 0 → {"ok":true,"selectedRow":0,"selectedRowText":"✓", …} ``` Fixes #3928. 💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
#3961) ## Release documentation prep for **v26.7.1** (2026-07-02) 29 commits since v26.6.5. Docs-only + version bump — no code behavior change. ### Version bump 26.6.5 → 26.7.1 The three canonical locations (per `AGENTS.md`): - `CMakeLists.txt:2` — `project(AetherSDR VERSION 26.7.1)` → `AETHERSDR_VERSION` (the app's version string) - `README.md` — Current version line - `AGENTS.md` — Current version line ### `CHANGELOG.md` Promoted `[Unreleased]` → **`## [v26.7.1] — 2026-07-02`** in house style (v-prefix, em-dash, "N commits since…" opener + headline), authored from all merged PRs and categorized: - **Added:** 3D stacked-trace spectrum (#3899), 3D dBm scale (#3937), in-process NVIDIA BNR + AFX download-on-demand (#3902), TX meter mouse-over readouts (#3936), SWR manual sweep range (#3885), CW sidetone capture (#3895), KiwiSDR metadata scaffolding (#3898), bridge verbs (#3920) - **Changed:** BNR container/NIM backend removed, 60 fps + per-pixel GPU FFT trace (#3958), FlexLib-sourced model capabilities (#3954/#2177), RX speaker-latency cut (#3897), Display pane sections (#3935) - **Fixed:** #3922, #3926, #3941, #3940, #3942, #3921, #3903, #3892, #3924, #3891, #3890, #3900, #3947 - Fresh empty `[Unreleased]` restored above the release block. Internal/CI/docs/self-fix PRs (#3931/#3916/#3934/#3929) omitted per house style. ### `ROADMAP.md` - Cycle header → "post-v26.7.1" - NVIDIA BNR removed from **In flight** (shipped this cycle) - Five v26.7.1 highlights prepended to **Recently shipped** ### `README.md` - Highlights: GPU spectrum bullet now notes the **per-pixel FFT trace at up to 60 fps** and the **3D stacked-trace** spectrum mode ### Reviewer notes - Touches protected paths (`*.md` + `AGENTS.md` Tier-1 + `CMakeLists.txt`) — needs `@aethersdr/infrastructure` sign-off. - Tagging `v26.7.1` and cutting the release build are **not** part of this PR (docs prep only). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
What
A batch of agent automation bridge improvements and a11y fixes surfaced while dogfooding the bridge against the radio-display, client-DSP, and net-scheduler subsystems (#3856, #3918). All in
src/core/AutomationServer.{cpp,h}plus one GUI a11y change.get dsp— the missing client-side AetherDSP modelget slicereports the radio-sidenr/nb/anf; there was no model for the client-side AudioEngine modules.get dspreports all six (NR2 / NR4 / MNR / DFNR / RN2 / BNR):active(the one enabled, mutually exclusive, or"none"),methods.<N>.{enabled, available}(available= compile-time backend gating), andtuning(engine getters read on the AudioEngine thread; NR2/NR4/DFNR-beta slider params merged fromAppSettings).streams resync— force the radio's authoritative display re-dumpLayer A (continued-UDP) and Layer B (
streams radio, cached) couldn't see a waterfall that lingers as a radio resource but stopped UDP and whose client view was already purged.streams resyncre-subscribes (sub pan all) to force the radio to re-dump every allocated display object, refreshing the Layer-B maps; re-pollstreams radioafter it settles. Doesn't pre-clear the maps, so firmware that doesn't re-dump leaves the inventory intact.invoke <view> selectRow <n>— item-view row selectioninvokecouldn't select aQTableWidgetrow, so a dialog's row-scoped actions (Tune/Edit/Remove/Disable) weren't drivable.selectRowsets the current index and a full-row selection (covers handlers readingcurrentRow()orselectedItems()), bounds-checked, echoingselectedRow+selectedRowText.window <maximize|restore|minimize|fullscreen> [target]+windowStateresizeonly ever set explicit geometry, so an un-maximize was unverifiable. Thewindowverb drives top-level window state anddumpTreenow carrieswindowStateto assert it.TX-guard false-positive on "tune" (#3918)
isTransmitControl()'s button-name fallback denied any button whose tokens includedtune/atu/vox, blocking the RX-only "Tune Now" button (net/spot retune — no keying). The QAction path had already narrowed tomox/ptt/transmit/cwx; the button path hadn't. Aligned them. Every genuine keying TUNE/ATU button (TxApplet,AtuPreTuneDialog) carries the authoritativemarkTxKeying()marker, which the positive check still honors — so no real protection is lost.a11y: name the six DSP method buttons + dumpTree check-state
The selector buttons (NR2 … BNR) are checkable, so
dumpTreereported every one asvalue:"checked"/"unchecked"with no label/accessibleName. They now set a stableobjectName(dspMethodBtnNR2…) + descriptiveaccessibleName(satisfiestools/check_a11y.py), anddumpTreenow surfaces a checkable button'stext+checkedbool (helps every checkable button).Proof — all verbs proven live (headless,
QT_QPA_PLATFORM=offscreen, no radio)window maximize/restore/fullscreenwindowStateboth flipmaximized/normal/fullscreen; bad action → errorinvoke "Scheduled nets" selectRow 0ok, selectedRow:0, selectedRowText:"✓"; out-of-range →row 0 out of range [0,0)invoke "Tune Now"(noALLOW_TX)ok(deferred) — no longer blocked; was a TX-guard false-positiveget dspactive:"none", per-methodenabled/available, fulltuningblockdumpTreeDSP buttonsobjectName=dspMethodBtn*,accessibleName,text,checkedThe TX-guard + selectRow proof drove the real Net Scheduler dialog end-to-end (add a test net →
selectRow 0→ "Tune Now" now enabled + invokable → removed the test net), leaving the user's schedule empty. Built clean (RelWithDebInfo).Files
src/core/AutomationServer.{cpp,h}—get dsp,streams resync,selectRow,window,windowState, TX-guard fix, dumpTree check-state, docstringssrc/models/RadioModel.{cpp,h}—resyncDisplayInventory()src/gui/AetherDspWidget.cpp— objectName/accessibleName on the six DSP buttons💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat