Skip to content

fix(tgxl): announce refused relay commands again. Principle XI. - #6138

Merged
ten9876 merged 2 commits into
mainfrom
fix-tgxl-refused-notice
Oct 4, 2026
Merged

ten9876 merged 2 commits into
mainfrom
fix-tgxl-refused-notice

Conversation

@ten9876

@ten9876 ten9876 commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator

Since #6008 merged (4d5b9893), a TGXL reached by IP only refuses OPERATE / STANDBY / BYPASS with no notice. MainWindow no longer connects TunerModel::relayedCommandRefused to showUnsupportedControlNotice(), the hookup #6044 added.

Cause. My fallback rework on #6008 (071ddd0b) replaced the wiring block between two comment markers in MainWindow_Wiring.cpp, and #6044's connect sat inside that block. The #6027 author found it while replaying their branch.

Change

  • src/gui/MainWindow_Wiring.cpp: the fix(gui): non-Flex controls work or refuse aloud. Principle VIII. #6044 connect is restored verbatim, right after the fallback block.
  • tests/tgxl_refused_notice_wiring_test.cpp (new, registered in tests/tests.cmake): a source pin that the connection exists and raises the notice. MainWindow can't be constructed in a test, so this is the one claim no behavioural seam reaches. TunerModel's emission is already covered by tuner_model_test.

Proof

  • The pin fails on main's current MainWindow_Wiring.cpp ("MainWindow connects TunerModel::relayedCommandRefused", "the refusal raises the unsupported-control notice") and passes with this change.
  • AetherSDR builds on Linux.

Constitution principle honored

  • Principle XI (Fixes Are Demonstrated): the regression is reproduced by the new test and shown fixed.
  • Commits are signed.

🤖 Generated with Claude Code

MainWindow connects TunerModel::relayedCommandRefused to
showUnsupportedControlNotice() again (#6044). The #6008 fallback rework
replaced the wiring block that held it, so since 4d5b989 a TGXL reached by
IP only refused OPERATE / STANDBY / BYPASS with no notice.

tgxl_refused_notice_wiring_test pins the connection in the wiring source,
the one place no behavioural seam reaches. It fails on 4d5b989's source
and passes with this change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ten9876
ten9876 requested a review from a team as a code owner October 3, 2026 23:37

@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.

Issue fit

Yes. No linked issue, and GOVERNANCE.md does not want one here — this is a bug fix with a named root cause, not an architectural change. I verified the cause independently rather than taking the body's word for it: git show 4d5b9893 -- src/gui/MainWindow_Wiring.cpp removes exactly 19 lines from that file, and the only connect( among them is &TunerModel::relayedCommandRefused plus its four-line comment and showUnsupportedControlNotice() body — the auth-greeting block took its place in the same spot after setDirectConnection(&m_tgxlConn). The restore is verbatim: I diffed the eleven removed lines against the added hunk and they match character for character, comment included.

The fix is also complete, which I checked rather than assumed. #6044 added four refusal hookups (relayedCommandRefused, plus antennaChoiceRefused from SpectrumOverlayMenu, VfoWidget and RxApplet); the three antenna ones are all still present at head (MainWindow_Wiring.cpp:5266, :5693, :7163), so this was the sole casualty and nothing else from that PR needs restoring.

Scope

File What it changes Claimed? Verdict
src/gui/MainWindow_Wiring.cpp Restores the #6044 connect verbatim, after the fallback block Yes In scope
tests/tests.cmake Registers the new pin target Yes In scope — but see nit 1 for where the block landed
tests/tgxl_refused_notice_wiring_test.cpp New source pin Yes In scope

Everything in the diff is explained by the stated fix. No new public or protocol surface, no settings key, no capability, no changed default, no deleted guard (the - lines are only the hunk context). CHANGELOG.md is untouched, which is correct.

Blockers

None.

Nits (all non-blocking)

  • tests.cmake block placement — the new block landed between spot_label_menu_test's descriptive comment (3569–3571) and its add_executable (3581), so that comment now reads as documentation for tgxl_refused_notice_wiring_test. Inline comment with the fix.
  • The pin is a text search, with the fragility that implies — it would go red on a harmless move of the connect into another MainWindow translation unit, and green if the connect were made against a different TunerModel instance. That's inherent to a source pin and the body is honest about it; the precedent is well established in this repo (dbm_range_plausibility_test, pan_dimension_routing_test, split_qsy_settings_test, passive_spots_policy_test all read MainWindow_Wiring.cpp the same way). Inline note on the "});" scan.
  • What "announced again" means in practice. showUnsupportedControlNotice() latches on m_commandDroppedNoticeShown (#5263): one status-bar notice per connect session, reset on the RadioModel::connectionStateChanged(true) edge for any family, and the latch is shared with the commandDropped path — so a gate refusal elsewhere in the session consumes the TGXL notice too. With a non-Flex radio connected that is one notice per connect session; with no RadioModel session at all (direct presence only) it is one notice for the app run. Pre-existing and restored verbatim, not introduced here — worth stating so the maintainer knows the ceiling on the behaviour being restored.
  • The operator-visible text doesn't name the real cause. The message is "This radio doesn't support that control — nothing was sent to the radio." For a TGXL refusal the cause is "no Flex radio to relay it", which the qCWarning states precisely and the status bar does not. Also pre-existing from #6044 — but since the operator-facing announcement is the entire point of this PR, it's the one thing worth a follow-up.

What I tried to break

  • "Restored verbatim" — extracted the removed lines from 4d5b9893 and diffed them against the added hunk. Identical, including the four comment lines and the lcDevices wording. Nothing was quietly "improved" in the restore.
  • "The pin fails on main" — I can't build here, so I ran the test's exact predicate (indexOf("&TunerModel::relayedCommandRefused"), then indexOf("});", at), then contains("showUnsupportedControlNotice()")) in Python against both trees: main → both claims false, exit 1; PR head → both true, exit 0. The claim holds. Note this is a simulation of the predicate, not a run of the binary — the new test is not on the frozen 7-test per-PR gate (check_ci_test_gate.py: 7 selected, 7 frozen, unchanged), so the green build / check-macos / check-windows jobs compiled it but did not execute it. It runs at merge in full-suite and weekly under sanitizers, which matches #5405.
  • Double notice — wireMeters() has exactly one call site (MainWindow.cpp:2140), and no other translation unit connects relayedCommandRefused, so the restore cannot produce two notices per refusal. Moving the block ~50 lines later than its original position is also harmless: the signal can only fire on an operator action, long after either ordering has run.
  • Incomplete fix — enumerated every connect( #6008 removed from that file (exactly one) and every hookup #6044 added (four), and confirmed the other three survive at head. There is no second surface left silent.
  • Compile risk — lcDevices is used three other times in the same TU, so the restore needs no new include; check_test_registration.py and check_ci_test_gate.py both pass at head, and all 7 checks on 741f432 are green (CodeQL C++ is absent for the known disk-space reason in #6130, not anything here).
  • Lifetime and sockets — the lambda takes this as its context object with a member-owned sender, so no dangling on teardown. The new test opens no socket (QFile only), and none is added or removed anywhere in the diff.

Findings above are reasoned from the code and from git history, not reproduced at runtime: I have no build, no test execution and no way to drive the GUI, so I could not observe the notice actually appearing in the status bar.

Recommendation

Approve with nits. The regression is real, the cause is confirmed by archaeology rather than assertion, the restore is byte-identical, and the fix is complete across every sibling hookup. The only change I'd ask for before merge is moving the tests.cmake block so spot_label_menu_test keeps its comment; the rest are observations for the maintainer and follow-ups, not merge conditions. I would not ask for a behavioural test here — MainWindow has no construction seam, the source-pin pattern has clear precedent, and tuner_model_test already covers the emission side.

Thanks for catching this one and for naming your own PR as the cause in the body — that's the kind of disclosure that makes the archaeology cheap for the next reader.


🤖 aethersdr-agent · cost: $8.0893 · model: claude-opus-5


GitHub rejected the inline anchors (GitHub API 422: Unprocessable Entity); findings below:

tests/tests.cmake:3572
The new block landed between spot_label_menu_test's descriptive comment (3569–3571: "Right-click on a client-side spot label offers Remove Spot … offscreen QMenu, socket-free") and that target's add_executable at 3581. As merged, the spot comment reads as documentation for tgxl_refused_notice_wiring_test, and the next reader has to diff to find out otherwise.

No suggestion fence because the fix spans lines outside this diff: either move this whole block (3572–3579) down past spot_label_menu_test's set_tests_properties, or move the three spot comment lines down to sit against their own add_executable. Cosmetic, but these comments are the only index of what each of 645 targets covers.

tests/tgxl_refused_notice_wiring_test.cpp:34-38
Worth naming what this pin does and doesn't catch — the header comment is already honest, so this is confirmation rather than a complaint.

I ran these three predicates against both trees to check the body's fail-before claim: on main the indexOf returns -1 and both checks go red; at this head both pass. So the pin does bite.

Two limits it inherits from being a text search:

  • A harmless move of the connect into another MainWindow TU (MainWindow_Session.cpp already carries the commandDropped twin) turns this red with the behaviour intact.
  • indexOf("});", at) finds the lambda's own close only because the body has no nested });. Adding one — a QTimer::singleShot, a nested connect — silently shrinks the window the contains() searches, so the check could pass on a body that no longer calls the notice. Not worth hardening today; just the thing to watch if that lambda ever grows.

The pattern itself has clear precedent here: dbm_range_plausibility_test, pan_dimension_routing_test, split_qsy_settings_test and passive_spots_policy_test all pin claims in MainWindow_Wiring.cpp the same way.

src/gui/MainWindow_Wiring.cpp:6389-6399
Verified verbatim against what 4d5b9893 removed — the eleven lines match character for character, comment included, so nothing was quietly reworded in the restore. Landing ~50 lines later than the original spot (it was directly after setDirectConnection(&m_tgxlConn)) is harmless: the signal can only fire on an operator action, long after either ordering has run. wireMeters() has one call site (MainWindow.cpp:2140) and no other TU connects this signal, so there's no double notice.

One thing worth knowing about the ceiling on what this restores, since the operator-facing announcement is the point of the PR: showUnsupportedControlNotice() latches on m_commandDroppedNoticeShown (#5263) and that latch is shared with the commandDropped path. With a radio connected the operator gets one notice per connect session — and a capability-gate refusal anywhere else in that session consumes it first. With direct presence and no RadioModel session, the latch never resets, so it's one notice for the app run. All pre-existing, none of it introduced here.

Separately: the qCWarning names the real cause precisely ("need a Flex radio to relay them"), but the status-bar text the operator actually sees is the generic "This radio doesn't support that control." Also from #6044, so out of scope for a verbatim restore — but it's the natural follow-up if this refusal is worth announcing at all.

@ten9876
ten9876 merged commit 32fa50e into main Oct 4, 2026
7 checks passed
@ten9876
ten9876 deleted the fix-tgxl-refused-notice branch October 4, 2026 00:16
on8st added a commit to on8st/AetherSDR that referenced this pull request Oct 4, 2026
main gained aethersdr#6138, aethersdr#6115 and aethersdr#6027 since this branch was cut. No
textual conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
on8st added a commit to on8st/AetherSDR that referenced this pull request Oct 4, 2026
main gained aethersdr#6138, aethersdr#6115 and aethersdr#6027 since this branch was cut. No
textual conflict; the touchpoint manifest is regenerated in the next
commit.

Co-Authored-By: Claude Opus 5.5 <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.

1 participant