Keep the RTTY decoder pane closed once dismissed (#5353). Principle V. - #5379
Keep the RTTY decoder pane closed once dismissed (#5353). Principle V.#5379aethersdr-agent[bot] wants to merge 1 commit into
Conversation
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>
CI: one failing check — the hardcoded-colour ratchetEverything that compiles this PR is green: I reproduced it locally against the PR's merge base ( What tripped itNo new colours were introduced — auto* rttyDecodeLbl = new QLabel("RTTY Decode:");
rttyDecodeLbl->setStyleSheet(kLabelStyle); // ← +1 call siteBecause it's a call-site count, reusing the existing Suggested fixFold the two labels in that Digital group through one small helper — same pattern as the // 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: 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 — 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 🤖 aethersdr-agent · cost: $3.7215 · model: claude-opus-5 |
ten9876
left a comment
There was a problem hiding this comment.
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; thatenabledlives in the nested object (Principle V); that the test linksaethercore(no consumers-list obligation); and the exact CI failure (+1 setStyleSheetcall 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); |
There was a problem hiding this comment.
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.
Summary
Fixes #5353
What was changed
Keep the RTTY decoder pane closed once dismissed (#5353). Principle V.
Files modified
src/gui/MainWindow.cppsrc/gui/MainWindow.hsrc/gui/MainWindow_DigitalModes.cppsrc/gui/PanadapterApplet.cppsrc/gui/RadioSetupDialog.cppsrc/gui/RttyDecodeSettings.htests/rtty_decode_settings_test.cpptests/tests.cmakeGenerated by AetherClaude (automated agent for AetherSDR)
🤖 aethersdr-agent · cost: $8.1352 · model: claude-opus-5