Skip to content

fix(gui): relabel the split badge from the slice role on every update (#4051)#4262

Merged
jensenpat merged 1 commit into
aethersdr:mainfrom
Ozy311:fix/4051-split-badge-relabel
Jul 17, 2026
Merged

fix(gui): relabel the split badge from the slice role on every update (#4051)#4262
jensenpat merged 1 commit into
aethersdr:mainfrom
Ozy311:fix/4051-split-badge-relabel

Conversation

@Ozy311

@Ozy311 Ozy311 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

The split/swap badge in the VFO flag is one QPushButton whose click handler dispatches on its text (VfoWidget.cpp ~687). updateSplitBadge() sets "SWAP" in its TX-slice branch — and that is the only setText after construction, so the text is one-way for the widget's lifetime. After the split pair is torn down, MainWindow::updateSplitState() correctly re-styles the badge to the ghosted idle look, but the stale "SWAP" text keeps every click emitting swapRequested() instead of splitToggled() — Split can never be re-enabled from the flag without reconnecting.

This change relabels the badge unconditionally from the slice's current role at the top of updateSplitBadge(): isTxSlice ? "SWAP" : "SPLIT".

Why

  • Fixes the reported trap: Split → close VFO B → the flag still says SWAP and Split is unreachable (Split action is replaced by "Swap" after closing VFO B, preventing Split from being re-enabled #4051).
  • Also fixes a second, unreported symptom of the same root cause: after a swap, the slice that becomes the RX-split member keeps the stale "SWAP" text under the red RX-split style — mislabeled and misdispatching.
  • The label now reconciles to the model state on every update, same as the styling already does.

Scope

  • 6 lines added, 1 removed, in src/gui/VfoWidget.cpp (updateSplitBadge())
  • No protocol / persistence change; no styling change (per-branch ThemeManager stylesheets untouched)

Constitution principle honored

Principle II — The Radio Is Authoritative On Live State. The badge's dispatch text is client-remembered state; before this fix it could disagree with the radio's actual split state (no split pair active) and the widget kept trusting its remembered value. The fix re-derives the label from the reconciled model state on every updateSplitState() pass, so the UI mirrors the radio instead of its own history.

Test plan

  • Local incremental ninja -C build clean on Linux (Nobara 43, Qt 6.10.3, gcc 15.2.1)
  • Scripted A/B on a live FLEX-6700 (RX-only) via the Automation Bridge — the same script (assert SPLIT → click badge → assert SPLIT(rx)+SWAP(tx) → remove the created slice → assert SPLIT) run against pre-fix main (8c973d7b) and this branch:
Assert pre-fix 8c973d7b this branch
mid-split: RX badge SPLIT, TX badge SWAP ❌ both read SWAP
after teardown: badge returns to SPLIT ❌ stuck SWAP (#4051)
A/B transcript (badge text read from the live widget tree via dumpTree; slice teardown scoped to the slice the script itself created)
[base-6700] PASS badge reads SPLIT before split got 'SPLIT'
[base-6700] PASS split created a second slice slices=2
[base-6700] FAIL pair shows SPLIT(rx)+SWAP(tx) [(1, 'SWAP', ...), (2, 'SWAP', ...)]
[base-6700] PASS back to one slice slices=1
[base-6700] FAIL #4051: badge reads SPLIT after teardown (stale SWAP = bug) got 'SWAP'

[fix-6700]  PASS badge reads SPLIT before split got 'SPLIT'
[fix-6700]  PASS split created a second slice slices=2
[fix-6700]  PASS pair shows SPLIT(rx)+SWAP(tx) [(1, 'SPLIT', ...), (2, 'SWAP', ...)]
[fix-6700]  PASS back to one slice slices=1
[fix-6700]  PASS #4051: badge reads SPLIT after teardown (stale SWAP = bug) got 'SPLIT'
  • grab_widget PNGs captured at each step (before / mid-split / after) on both builds — the after-teardown pair shows ghosted SWAP (base) vs ghosted SPLIT (fix) on the same flag

Note on unit tests: VfoWidget compiles only into the AetherSDR executable target (GUI_SOURCES), not aethercore, so a standalone badge test would need to enumerate the widget's full GUI dependency closure as a new test target — disproportionate for a one-line relabel. The existing vfo_flag_placement_test deliberately covers only the class's static helpers for the same reason.

Checklist

  • Commits are signed (docs/COMMIT-SIGNING.md)
  • No new flat-key AppSettings calls (Principle V)
  • All meter UI uses MeterSmoother (Principle II conventions — no meter code touched)

Closes #4051


73, Ozy K6OZY
AI compute partnership: cloaked.agency — (model: claude-fable-5)

…aethersdr#4051) — Principle II.

The split/swap badge is one QPushButton whose click handler dispatches on
its text, but updateSplitBadge() only ever wrote "SWAP" (TX branch) — the
text was one-way for the widget lifetime. After split teardown the badge
was re-styled to idle but kept the stale "SWAP", so every click emitted
swapRequested() and Split could never be re-enabled from the flag. The
same staleness mislabeled the RX-split member "SWAP" after the transient
TX handoff during split creation.

Relabel unconditionally from the current role at the top of the function:
isTxSlice ? "SWAP" : "SPLIT". Styling untouched.

Verified via the Automation Bridge on a FLEX-6700 (RX-only): scripted
split -> teardown A/B; pre-fix main (8c973d7) fails both asserts (badge
stuck SWAP), this branch passes all.

Co-authored-by: Don @ cloaked.agency <don@cloaked.agency>
@Ozy311
Ozy311 marked this pull request as ready for review July 16, 2026 05:03
@Ozy311
Ozy311 requested a review from a team as a code owner July 16, 2026 05:03

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

Clean, correct, and well-scoped. I traced the root cause independently: the click handler (VfoWidget.cpp:686) dispatches on m_splitBadge->text(), and pre-fix the only setText after construction was "SWAP" in the TX branch — a one-way latch, exactly as described. Relabeling unconditionally from isTxSlice at the top of updateSplitBadge() reconciles the dispatch text to the model state on every pass, mirroring what the styling already does. m_splitBadge is constructed unconditionally in the ctor, so no null risk; the derivation also fixes the post-swap RX-member mislabel you called out. No convention issues (no QSettings/AppSettings, no leaks), and the live A/B transcript on the 6700 is convincing. The unit-test note is a reasonable call given the widget's GUI dependency closure.

Nothing blocking. Nice fix — thanks for the thorough write-up.

Non-blocking note:

  • The badge's accessibleName/accessibleDescription ("Split mode" / "Toggle split transmit frequency", set once at VfoWidget.cpp:1288-1289, outside this diff) don't track the SWAP state, so a screen reader announces "Split" while the button acts as Swap. Pre-existing and out of scope here, but if you touch this again it'd be a natural companion fix to derive them from the same role.

🤖 aethersdr-agent · cost: $4.6459 · model: claude-opus-4-8

@jensenpat jensenpat self-assigned this Jul 17, 2026

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

Reviewed the exact head and its clean combined merge with current main. updateSplitBadge() already receives the model-derived role, and resetting the dispatch text on every update fixes both teardown and post-swap stale state without changing styling or protocol behavior. Full AetherSDR build passed with 8 parallel jobs; PR CI and live FLEX-6700 A/B automation are green. No blockers.

@jensenpat
jensenpat merged commit a33abdf into aethersdr:main Jul 17, 2026
7 checks passed
@Ozy311
Ozy311 deleted the fix/4051-split-badge-relabel branch July 19, 2026 01:22
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.

Split action is replaced by "Swap" after closing VFO B, preventing Split from being re-enabled

2 participants