Skip to content

Keep the RTTY decoder pane closed once dismissed (#5353). Principle V. - #5379

Open
aethersdr-agent[bot] wants to merge 1 commit into
mainfrom
aetherclaude/issue-5353
Open

Keep the RTTY decoder pane closed once dismissed (#5353). Principle V.#5379
aethersdr-agent[bot] wants to merge 1 commit into
mainfrom
aetherclaude/issue-5353

Conversation

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Summary

Fixes #5353

What was changed

Keep the RTTY decoder pane closed once dismissed (#5353). Principle V.

Files modified

  • src/gui/MainWindow.cpp
  • src/gui/MainWindow.h
  • src/gui/MainWindow_DigitalModes.cpp
  • src/gui/PanadapterApplet.cpp
  • src/gui/RadioSetupDialog.cpp
  • src/gui/RttyDecodeSettings.h
  • tests/rtty_decode_settings_test.cpp
  • tests/tests.cmake
 src/gui/RadioSetupDialog.cpp        |  20 +++++++
 src/gui/RttyDecodeSettings.h        |  76 ++++++++++++++++++++++++
 tests/rtty_decode_settings_test.cpp | 112 ++++++++++++++++++++++++++++++++++++
 tests/tests.cmake                   |   9 +++
 8 files changed, 255 insertions(+), 37 deletions(-)

Generated by AetherClaude (automated agent for AetherSDR)


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

RTTY pane visibility was derived entirely from the slice mode:
refreshRttyDecodeState() computed `slice->mode() == "RTTY"` and both
showed the pane and started the decoder. The pane's ✕ button only
hid the widget and stopped the decoder — nothing recorded that the
operator had dismissed it — so the next refresh recomputed the same
`true` and put it back.

Callers that fire during normal operation: setActiveSlice(),
setActivePanApplet(), the active slice's modeChanged, and
rttyMarkChanged / rttyShiftChanged. That last pair is the
frequency half of the report: on a band change the radio resets
rtty_mark, SliceModel::updateFromStatus() emits rttyMarkChanged,
and the pane reopens without the operator touching a slice.

"The decoder is available for this slice" and "the operator wants
the window" are separate states. This adds the second one:

- New src/gui/RttyDecodeSettings.h — the decoder's owned config
  object under the existing AppSettings["RttyDecoder"] root key,
  carrying `enabled` (default True, so nothing changes for anyone
  who never closes the pane) alongside the `sensitivity` field that
  already lived there. The read/modify/write-whole shape means a
  write to either field never drops the other (Principle XIV).
  PanadapterApplet's local sensitivity helpers now route through it
  so the object has one owner (Principle V).
- refreshRttyDecodeState() gates on `isRtty && enabled()`, exactly
  as refreshCwDecodeState() gates on `isCw && anyOn`, and stops the
  decoder when the pane is off.
- New MainWindow::onRttyPanelCloseRequested() replaces the raw
  rttyPanelCloseRequested → RttyDecoder::stop connection: it
  persists the dismissal, then refreshes (which performs the stop).
- Re-enable control: an "RTTY Decode" toggle in RadioSetupDialog's
  Digital group, mirroring the CW Decode RX/TX buttons, with
  refreshRttyDecodeState() called on dialog close beside the
  existing refreshCwDecodeState(). Before this there was no way to
  open the pane on purpose at all, so making ✕ sticky without it
  would have been a one-way door.

Test: tests/rtty_decode_settings_test.cpp asserts the default, that
the disabled state survives repeated refreshes and a settings
reload, that the re-enable works, that neither field clobbers the
other, and that the stored shape stays one nested object with no
new flat key. Passes locally (14/14).

CodeGuard: input-validation — sensitivity is clamped to 0..100 on
read and `enabled` falls back to its safe default on malformed or
hand-edited JSON, so a corrupt settings object cannot produce an
out-of-range threshold.

Blast radius: risk_score=0.215, 12 high-risk affected (top:
MainWindow::MainWindow, MainWindow::wirePanadapter,
MainWindow::wireRadioModel). Those are structural bridges reached
transitively through MainWindow construction and wiring, not
signature dependents: refreshRttyDecodeState() keeps its signature
and every caller keeps calling it unchanged — only the boolean it
computes gains a second term. The one behavioral change visible
downstream is that an RTTY slice no longer forces the pane open,
which is the reported defect. PanadapterApplet's edits are
internal-linkage helper removal plus a lambda body; its public
signals and slots are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@aethersdr-agent
aethersdr-agent Bot requested a review from a team as a code owner September 1, 2026 17:03
@aethersdr-agent
aethersdr-agent Bot enabled auto-merge (squash) September 1, 2026 17:03
@aethersdr-agent

Copy link
Copy Markdown
Contributor Author

CI: one failing check — the hardcoded-colour ratchet

Everything that compiles this PR is green: build, check-macos, check-windows, and both CodeQL jobs all passed on 05b5a20. The only failure is the Hardcoded-colour ratchet step of Static checks. This is a real gate on the diff, not CI infrastructure — but it's a one-line fix.

I reproduced it locally against the PR's merge base (eeaa8d44):

=== ratchet (vs PR base) ===
  OK   unique_colours       613  (base 613, +0)
  OK   total_references    2724  (base 2724, +0)
  OVER setstylesheet       1091  (base 1090, +1)

FAIL: this PR raises the hardcoded-colour count above its base.
  setstylesheet: 1091 > 1090  (+1)

What tripped it

No new colours were introduced — unique_colours and total_references are both flat. The metric that rose is setstylesheet, which counts setStyleSheet( call sites, not colours. The new one is in src/gui/RadioSetupDialog.cpp at the RTTY Decode row:

auto* rttyDecodeLbl = new QLabel("RTTY Decode:");
rttyDecodeLbl->setStyleSheet(kLabelStyle);   // ← +1 call site

Because it's a call-site count, reusing the existing kLabelStyle constant doesn't help — the ratchet is deliberately unmoved by that, since the goal (RFC #3076) is to shrink the number of places that style widgets directly. The gate itself is a delta against the merge base, so it isn't a stale frozen baseline: .github/workflows/static-checks.yml:174, scanner at tools/audit_colours.py.

Suggested fix

Fold the two labels in that Digital group through one small helper — same pattern as the mkTogBtn lambda already sitting a few hundred lines above it. That removes one site and adds one, netting zero:

// One styling site for both labels in this group: the hardcoded-colour
// ratchet counts setStyleSheet() call sites, so a new label must reuse
// an existing one rather than add its own.
auto mkLbl = [&](const QString& text) {
    auto* l = new QLabel(text);
    l->setStyleSheet(kLabelStyle);
    return l;
};

auto* markLbl = mkLbl("RTTY Mark Default:");
grid->addWidget(markLbl, 0, 0);
...
auto* rttyDecodeLbl = mkLbl("RTTY Decode:");
grid->addWidget(rttyDecodeLbl, 1, 0);

I applied exactly that to a scratch copy of the head commit and re-ran the check:

=== ratchet (vs PR base) ===
  OK   unique_colours       613  (base 613, +0)
  OK   total_references    2724  (base 2724, +0)
  OK   setstylesheet       1090  (base 1090, +0)

You can verify locally before pushing:

git worktree add --detach /tmp/colour-base $(git merge-base HEAD origin/main)
python3 tools/audit_colours.py --src src --compare-src /tmp/colour-base/src --summary-only --strict

(The other route the tool suggests — ThemeManager::instance().applyStyleSheet(...) with a {{color.*}} token — is the longer-term destination and is also not counted, but the lambda is the smaller change and matches what the rest of buildPhoneCwTab() does today.)

No Copilot or other reviewer comments were posted on this PR, so there's nothing else flagged that bears on the failure.

Nothing wrong with the actual fix — the RttyDecodeSettings split of "decoder is available for this slice" from "the operator wants the window" reads well, and the test that pins the disabled state surviving a re-read is the right thing to assert. Thanks for taking this one on. 👍


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

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

Issue fit

#5353: the RTTY decoder pane reopened on every frequency/slice change because nothing persisted the operator's dismissal — visibility was recomputed from mode alone. The fix is correct and well-reasoned: the ✕ now routes through onRttyPanelCloseRequested(), which persists RttyDecodeSettings::setEnabled(false) before refreshing, and refreshRttyDecodeState() gates on isRtty && enabled() — availability AND intent, exactly mirroring the existing refreshCwDecodeState() (isCw vs. CwDecodeSettings) precedent the comment cites. enabled defaults True, so a session that never dismisses the pane behaves as before. This genuinely closes the reopen-on-refresh hole (slice switch, active-pan change, and the rtty_mark band-change echo are all named and covered).

Principle V is honored: RttyDecodeSettings reads/writes one nested RttyDecoder object through a single setValue()+save(), the new enabled field lives in that object (not a flat key), and the pre-existing Mark/Shift/Baud/Reverse flat keys are left grandfathered rather than churned. The extraction into a shared header with a socket-free unit test is the right shape, and rtty_decode_settings_test links aethercore, so it correctly needs no AETHER_SETTINGS_CONSUMERS entry.

Scope

Eight files, all on-issue. No CHANGELOG.md. Preflight: no sockets, pure settings test.

Blockers

1. Red CI — the new label trips the hardcoded-colour ratchet (inline). Static checks fail: setstylesheet 1091 > 1090 (+1). The RTTY settings row added to RadioSetupDialog styles its label with rttyDecodeLbl->setStyleSheet(kLabelStyle) — a new raw setStyleSheet() call site against a static const QString literal, which docs/style/theme-style-guide.md bans (every colour resolves through a ThemeManager token) and which the ratchet in static-checks.yml enforces. The dialog already has the themed path (ThemeManager::applyStyleSheet with {{color.text.*}} tokens, at :439/:652/:665) — use it for this label so no new raw call site is added. Merge-gating until green.

Nits (non-blocking)

  • Re-enabling after dismissal is via the RadioSetupDialog settings row; that matches how CW decode works, but the issue reporter's mental model was a window they close — worth confirming the settings toggle is discoverable enough that "I closed it and now can't get it back" doesn't become the next report. Not a code change, just a UX check for you.

What was verified vs read

  • Verified by me in the PR head: the dismiss→persist→refresh path and the isRtty && enabled() gate; that enabled lives in the nested object (Principle V); that the test links aethercore (no consumers-list obligation); and the exact CI failure (+1 setStyleSheet call site at the new label, with the dialog's own themed path as the fix).
  • The automated pass for this PR misfired (it returned #5364's SpectralNR/reset findings — the second such misfire in this batch) and was discarded; this is a direct manual pass.
  • Not run: no bridge session (settings-persistence + panel-visibility state; the socket-free unit test is the right vehicle and is present). CI's own red check is the empirical signal for the blocker.

Recommendation: request changes — the fix itself is correct and mergeable once the one new setStyleSheet is routed through the themed path to clear CI.

// Decode RX/TX toggles above. MainWindow re-evaluates panel and
// run state on dialog close via refreshRttyDecodeState().
auto* rttyDecodeLbl = new QLabel("RTTY Decode:");
rttyDecodeLbl->setStyleSheet(kLabelStyle);

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.

Blocker — this new raw setStyleSheet is the red CI. Static checks fail on setstylesheet 1091 > 1090 (+1): this call site is the +1, and a raw setStyleSheet(kLabelStyle) against a literal violates the theme-token rule the ratchet enforces. The dialog already styles labels the themed way (ThemeManager::applyStyleSheet with {{color.text.*}} tokens at :439/:652/:665) — route this label through that instead of adding a new raw call site, and CI goes green. The persistence fix itself is correct and unaffected.

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.

The RTTY windows won't stay disabled

1 participant