Skip to content

feat(bridge): get dsp, streams resync, selectRow/window verbs, TX-guard fix + a11y (#3856, #3918)#3920

Merged
ten9876 merged 2 commits into
aethersdr:mainfrom
jensenpat:feat/3856-bridge-display-dsp-verbs
Jun 30, 2026
Merged

feat(bridge): get dsp, streams resync, selectRow/window verbs, TX-guard fix + a11y (#3856, #3918)#3920
ten9876 merged 2 commits into
aethersdr:mainfrom
jensenpat:feat/3856-bridge-display-dsp-verbs

Conversation

@jensenpat

@jensenpat jensenpat commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

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 model

get slice reports the radio-side nr/nb/anf; there was no model for the client-side AudioEngine modules. get dsp reports 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), and tuning (engine getters read on the AudioEngine thread; NR2/NR4/DFNR-beta slider params merged from AppSettings).

streams resync — force the radio's authoritative display re-dump

Layer 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 resync re-subscribes (sub pan all) to force the radio to re-dump every allocated display object, refreshing the Layer-B maps; re-poll streams radio after 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 selection

invoke couldn't select a QTableWidget row, so a dialog's row-scoped actions (Tune/Edit/Remove/Disable) weren't drivable. selectRow sets the current index and a full-row selection (covers handlers reading currentRow() or selectedItems()), bounds-checked, echoing selectedRow + selectedRowText.

window <maximize|restore|minimize|fullscreen> [target] + windowState

resize only ever set explicit geometry, so an un-maximize was unverifiable. The window verb drives top-level window state and dumpTree now carries windowState to assert it.

TX-guard false-positive on "tune" (#3918)

isTransmitControl()'s button-name fallback denied any button whose tokens included tune/atu/vox, blocking 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. Every genuine keying TUNE/ATU button (TxApplet, AtuPreTuneDialog) carries the authoritative markTxKeying() 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 dumpTree reported every one as value:"checked"/"unchecked" with no label/accessibleName. They now set a stable objectName (dspMethodBtnNR2 …) + descriptive accessibleName (satisfies tools/check_a11y.py), and dumpTree now surfaces a checkable button's text + checked bool (helps every checkable button).

Proof — all verbs proven live (headless, QT_QPA_PLATFORM=offscreen, no radio)

Verb Result
window maximize/restore/fullscreen response and dumpTree windowState both flip maximized/normal/fullscreen; bad action → error
invoke "Scheduled nets" selectRow 0 ok, selectedRow:0, selectedRowText:"✓"; out-of-range → row 0 out of range [0,0)
invoke "Tune Now" (no ALLOW_TX) ok (deferred) — no longer blocked; was a TX-guard false-positive
get dsp active:"none", per-method enabled/available, full tuning block
dumpTree DSP buttons all six carry objectName=dspMethodBtn*, accessibleName, text, checked

The 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, docstrings
  • src/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

jensenpat and others added 2 commits June 29, 2026 23:41
…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>
@jensenpat jensenpat changed the title feat(bridge): get dsp, streams resync, DSP-button a11y + dumpTree check-state (#3856) feat(bridge): get dsp, streams resync, selectRow/window verbs, TX-guard fix + a11y (#3856, #3918) Jun 30, 2026
@jensenpat jensenpat marked this pull request as ready for review June 30, 2026 19:00
@jensenpat jensenpat requested a review from a team as a code owner June 30, 2026 19:00

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the aetherTxKeying marker first, and every genuine keying TUNE/ATU button (TxApplet.cpp:235/259, AtuPreTuneDialog.cpp:303/341/348) carries markTxKeying(), so dropping tune/atu/vox from 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. doStreams guards m_radioModel at the top before resyncDisplayInventory(); get dsp guards m_audioEngine.
  • topLevelWindowForTarget refactor cleanly dedups doResize/doWindow; static→static resolveWidget call is valid.
  • selectRow is bounds-checked and sets both current-index and full-row selection; the snapshot's threading mirrors the existing audioSnapshotOnObjectThread pattern (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 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ten9876 ten9876 merged commit 16ea513 into aethersdr:main Jun 30, 2026
6 checks passed
ten9876 added a commit to rfoust/AetherSDR that referenced this pull request Jun 30, 2026
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>
ten9876 added a commit that referenced this pull request Jun 30, 2026
…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>
jensenpat added a commit to jensenpat/AetherSDR that referenced this pull request Jun 30, 2026
…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>
ten9876 pushed a commit that referenced this pull request Jul 1, 2026
…#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>
ten9876 added a commit that referenced this pull request Jul 2, 2026
#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants