Repository navigation
feat(gui): radio tab shows an AT chip for adaptive throttle and colours its dot by network quality. Principle VIII. - #6787
Conversation
…rs its dot by network quality. Principle VIII. The unified title bar folded the heartbeat lamp into the active radio tab's dot, which lost the lamp's quality cue and left adaptive throttle visible only as a glow tint. The connected tab's dot now takes the status bar's network-quality colour (and names the grade in its tooltip/accessible description), and an "AT" chip follows the radio's name while the adaptive frame-rate throttle is enabled: outlined when armed, filled in the cap's quality colour while a cap is in force. Bridge: radios.linkQuality, linkQualityColor, adaptiveThrottle, adaptiveFpsCap and tabs[].throttleChip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Thanks for this — the AT chip is a genuinely useful cue, the ours/session gating is carefully reasoned, and the WCAG 1.4.1 work (grade and throttle state spelled out in the tooltip and accessible description, not colour-only) is the right instinct. The code itself is clean; I tried several ways to break it and it survived all of them. My one blocker is about authority for a visual change, not about the implementation.
1. Issue fit
No linked issue and no RFC — search_issues for the adaptive-throttle indicator and for the heartbeat-lamp/tab-dot quality cue returns nothing but this PR. So I reviewed against the body's own stated intent. The body frames the change as restoring two cues lost when the unified title bar landed (#6198). One of those two premises does not hold — see Blocker 1. The AT chip half is sound: m_throttleFlashColor did exist pre-#6198 and the throttle really was visible only as a glow tint, so the chip addresses a real gap.
2. Scope
Everything in the diff is explained by the body; no unrelated files, no vendored or CI churn, no removed guards. One row is in scope but undisclosed as a visible change.
| File(s) | What it changes | Claimed? | Verdict |
|---|---|---|---|
RadioTabBar.cpp/.h |
AT chip, quality-coloured dot, 5 new bridge fields | Yes | In scope; the dot recolour is Blocker 1, the bridge surface is Decision 2 |
MainWindow.cpp/.h |
refreshAdaptiveThrottleChip(), quality push, fpsCapColor lambda → helper |
Yes | In scope |
MainWindowHelpers.cpp/.h |
adaptiveFpsCapColor() extracted |
Yes | In scope; thresholds byte-identical to the removed lambda, and it removes 3 hex literals |
ConnectionPanel.cpp/.h |
adaptiveThrottleEnabledChanged signal |
Yes | In scope; correctly under signals: |
TitleBar.cpp |
currentBeatColor() returns invalid instead of accent.success |
Partly | In scope, but changes the discovery pulse too — undisclosed (Nit 7) |
tests/unified_title_bar_test.cpp |
1 new block | Yes | In scope (Nit 1 on what it pins) |
| 4 doc files | chip/quality/bridge | Yes | In scope |
No socket test is added, modified, or removed by this PR — no QTcpServer/QUdpSocket/bind/listen/connectToHost and no fake peer in the changed test. Nothing to surface under the socket-test reporting rule.
3. Blockers
1. src/gui/RadioTabBar.cpp:317-319 — the quality-coloured dot is a new visual-design decision, not a restoration. The premise it rests on is not true, and GOVERNANCE.md requires an RFC for it.
The body's justification:
When the unified title bar landed (#6198), the standalone heartbeat lamp was folded into the active radio tab's status dot. That dropped … the lamp's network-quality colour
I checked the tree immediately before #6198 (5f09371e2^). The lamp never had a network-quality colour:
src/gui/TitleBar.cppat5f09371e2^contains zero occurrences of "quality" (57 KB file, so it is the real pre-change file).- The lamp was a 10×10
QLabel(TitleBar.h:166,TitleBar.cpp:133-138) whose own accessible description read:"Flashes green when radio discovery packets are received". Its colour states were grey/green/amber/red plusm_throttleFlashColor— no quality tier. networkQualityColor()at5f09371e2^had exactly the two consumers it has onmaintoday: the status-bar label (MainWindow.cpp:2248) and the diagnostics tooltip (MainWindowHelpers.cpp:182). The lamp was never one of them.
So #6198 dropped the throttle-tint prominence (real — the chip fixes it) but did not drop a quality cue. Moving the status bar's five-tier palette onto the tab dot is a new design choice: on main that dot means radio status, and after this PR a healthy connected radio on a Fair link shows amber and on a Poor link shows red — the same red family as the link-lost alarm.
GOVERNANCE.md, "What requires an RFC":
- Any change to visual design — colors, fonts, spacing, theme, icons
and the "does NOT require an RFC" list covers only "New applets or dialogs that don't change existing UX". This changes the meaning of an existing indicator. Sibling PRs in flight (#6753, #6757, #6759, #6762) all cite RFC #6226 for comparable work, so the project clearly gates this.
I'm not saying the idea is wrong — it may well be an improvement. It needs its own issue and a maintainer ruling rather than riding in under a restoration framing. Concretely: either land the AT chip alone (additive, fixes a cue that measurably did regress) and split the dot recolour into an RFC, or get an explicit ruling on the recolour before merge. Either way the body's #6198 sentence should be corrected.
4. Nits (all non-blocking)
tests/unified_title_bar_test.cpp:971-1020— neither visual claim is actually pinned. The block asserts tooltip strings and bridge ints only. Delete the quality branch fromdotColor()(RadioTabBar.cpp:317-319) and every new check still passes; likewisesetAdaptiveThrottle(true, 8, QColor("#cc9900"))asserts the tooltip andadaptiveFpsCapbut never that the chip tints. The dot is a solid-filled ellipse, so a centre-pixel probe ofgrab()is deterministic (unlike the glyph probes elsewhere in this suite, it isn't font-dependent).tests/unified_title_bar_test.cpp:985— thesessionhalf of the gate is untested. "another radio's tab does not claim this session's quality" passes onisCarrieralone; dropping&& !m_activeId.isEmpty()fromRadioTabBar.cpp:861leaves it green. Abar->setActiveRadio({})followed bycheck(!sessionTab->isThrottleChipShown(), …)would pin the disconnect-clears-chip path cheaply.src/gui/MainWindow.cpp:731-732— the comment is wrong. It says:// A cap can still be in force just after the box is unticked (it lifts at // the next quality update), so the chip stays until the cap is gone too.
RadioModel::applyAdaptiveFrameRate()returns early atsrc/models/RadioModel.cpp:8991whenAdaptiveThrottleEnabled != "True", so the lift at:9024is never reached — an already-applied cap lifts only on disconnect (:8639). That matcheslow-bandwidth-connections.md("reconnect to lift an already-applied cap") and the checkbox tooltip atConnectionPanel.cpp:1044. Theenabled || m_adaptiveFpsCap > 0behaviour is right; only the stated reason is wrong, and "just after" understates it — the chip stays filled for the rest of the session.src/gui/RadioTabBar.cpp:317-319— first hard-coded hex on the tab dot. Every other dot colour routes throughThemeManager:TitleBar::linkOverrideColor()usescolor.accent.danger/color.accent.warning, and the fallback usesstatusToken().networkQualityColor()returns literals (#00cc66,#00b4d8,#cc9900,#cc3333) calibrated against the status bar's background, now painted oncolor.titlebar.tab.active.background. A user theme can't restyle the dot any more.docs/a11y.mdwants 3:1 for non-text UI — worth stating the five colours' contrast against the tab background in both bundled themes rather than assuming the status bar's calibration carries over.src/gui/RadioTabBar.cpp:877-879— non-session tabs are pushed live chip state.setThrottleChip(ours && m_throttleEnabled, m_throttleCapColor, chipText)hands every tab the current tint and text even whenshownis false, so a cap change (8→4) misses the equality guard on every such tab and costs arefreshAccessibility()(three setters) plus a fullupdate()for no visible change. Also,chipTextis built withtr(...).arg(...)at:862-864on everyapplyLinkVisuals()call, and that runs once per pulse tick (:666) for the whole glow decay. Both go away with:tab->setThrottleChip(ours && m_throttleEnabled, ours ? m_throttleCapColor : QColor(), ours ? chipText : QString());docs/automation-bridge.md:3243-3247—radios.adaptiveThrottleis documented as "(enabled)" butrefreshAdaptiveThrottleChip()passesenabled || m_adaptiveFpsCap > 0, so after unticking the box with a cap in force the field readstruewhile the setting isfalse. Given the nit above that is the rest of the session. Worth wording it as "the chip is showing" rather than "enabled".src/gui/TitleBar.cpp:1669-1671— an undisclosed behaviour change. The removed line was:The consumer handles invalid correctly (return m_throttleFlashColor.isEmpty() ? ThemeManager::instance().color(this, QStringLiteral("color.accent.success"))
RadioTabBar.cpp:429,m_beatColor.isValid() ? m_beatColor : dot), so this is safe — but it also changes the discovery pulse, which previously glowed green over an amber override and now glows amber. Probably an improvement; it's explained only in a code comment, not the body.src/gui/RadioTabBar.cpp:223— pre-existing, out of scope, credit security-audit (CodeGuard).setToolTip(descriptionLine())passesm_entry.name(:194-195) unescaped, and Qt auto-detects rich text in tooltips, so a radio nickname containing markup renders as HTML. The repo already escapes one layer up (MainWindowHelpers.cpp:192calls.toHtmlEscaped()on the quality string). Fix is.toHtmlEscaped()on the name. This PR's additions to that tooltip aretr()constants and a fixed-table label, so it does not widen the surface — flagging only because you're in the function. I did not verify the agent's further claim that this could trigger a remote resource fetch, so please don't take that part as established.
5. What I tried to break (no finding)
Everything below is reasoned from reading the head checkout at /tmp/aetherclaude/pr-6787; I am headless, so nothing here was reproduced at runtime.
- Constructor ordering. The new
connect(m_connPanel, …)and seedrefreshAdaptiveThrottleChip()sit aroundMainWindow.cpp:2205-2207, butm_titleBar/m_connPanelare assigned at:5467/:5525— which looked like a null-senderconnect()that would silently never fire. Refuted: those lines are inbuildUI(), called at:1421, and the constructor spans:1085–<2618. Both pointers are live. (The diff's@@ … MainWindow::MainWindowheader is git's context heuristic, not the enclosing scope.) - The new signal's access specifier. The hunk header reads
public slots:; a signal declared underslots:would never be emittable. Refuted —ConnectionPanel.h:132-145puts it undersignals:with its siblings. - Stale quality/chip across a radio swap or disconnect. Two independent defences:
RadioModelemits bothadaptiveThrottleChanged(false, 0)(:8639) andnetworkQualityChanged("Off", 0)(:8740) on teardown, andsetActiveRadio("")→applyActiveState()→applyLinkVisuals()re-pushes to every tab, soours == falseclears both. A second radio can't inherit the first's grade. - Another writer of
AdaptiveThrottleEnabledbypassing the new signal.ConnectionPanel.cpp:1048is the only writer in the tree; the three other sites are reads. The signal covers every path. currentBeatColor()returning an invalidQColor. Only caller isTitleBar.cpp:1683; the sink atRadioTabBar.cpp:429already branches onisValid(). No null-colour paint.adaptiveFpsCapColor()drifting from the lambda it replaced. Identical:≤4 → #cc3333,≤8 → #cc9900, else#00b4d8.- Accessibility churn from the per-tick
applyLinkVisuals(). The early-return guards inRadioTab::setLinkQuality/setThrottleChipmeanrefreshAccessibility()fires only on a real change — correct for the carrier tab (the waste is on the other tabs, Nit 5). radioTabBar()null deref. Unconditionally constructed atTitleBar.cpp:227; the existing site atMainWindow.cpp:4837dereferences identically.- The security triage. CodeGuard's 97 findings are all false positives on this change — the CMDI-BACKTICK/AMP/PIPE and XSS-OBJECT hits are C++ comment prose (neither
RadioTabBar.cppnorConnectionPanel.cppcontains any shell invocation), the SSRF-PRIV/PII-IP hits aresetPlaceholderTextexamples and a test fixture, PATH-PROC is the fixed/proc/self/status, and PATH-UNC is JSON-escaped regex. Nothing real is introduced by this diff.
What I could not check: any runtime or visual behaviour (no build, no GUI); the actual contrast ratios in Nit 4; and unified_title_bar_test is not in ci.yml's per-PR ctest -R list, so the green checks prove it compiles, not that the new block passes — the body's local Windows run is the only evidence for it, and the body doesn't disclose that gap.
6. Recommendation
Needs maintainer decision. The implementation is solid and I found no correctness defect, but two calls aren't mine to make: (a) the quality-coloured dot is a visual-design change to an existing indicator whose stated justification is contradicted by the pre-#6198 source, and GOVERNANCE.md names exactly this category as RFC-gated; (b) five new bridge keys are public surface third parties will bind to, arriving without an issue.
Concrete next step: correct the #6198 sentence in the body, then either split the dot recolour into its own RFC and land the AT chip on its own — which I'd recommend, since the chip stands on a gap that genuinely did regress — or get @ten9876 to rule on the recolour directly. The nits are all cheap and worth folding in either way; Nits 1 and 3 are the two I'd most want fixed before merge.
🤖 aethersdr-agent · cost: $19.2804 · model: claude-opus-5
|
Cisco CodeGuard — static analysis of this PR (100 finding(s))
Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them. 🤖 aethersdr-agent · cost: $20.0887 · model: claude-opus-5 |
|
Antares Detector — candidate vulnerable file(s)
The NetworkDiagnostics implementation likely reads network packets and deserializes them; if it uses unsafe deserialization or untrusted network packet reads, it can lead to memory corruption or unsafe memory access. Localized by Cisco Foundation AI Antares-1B running locally in the AetherClaude sandbox, seeded by the Cartographer security map. Advisory only — please verify before acting. 🤖 aethersdr-agent · cost: $20.0887 · model: claude-opus-5 |
Live proof via the automation bridgeBuild: fork Windows Installer run 40263712412 on State was read from the bridge's Toggle sequence (one connected session)
The chip follows the checkbox immediately, through the new Cap in force (earlier session, throttle enabled, reconnected)
Notes
73, CJ (WT2P) 👨🏼💻 Co-authored by Claude Opus 5.5 |
…. Principle VIII. Operator feedback on aethersdr#6787: with adaptive throttle off the connection dot should stay the plain connected green, and any colour change should come with the AT chip that explains it. RadioTabBar now passes the quality to the session's tab only while the throttle is enabled; the bridge still publishes radios.linkQuality as the model's grade. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bridge re-proof for
|
| Step | linkQuality |
adaptiveThrottle |
throttleChip |
Dot | Tooltip |
|---|---|---|---|---|---|
| Connected, box unticked | Very Good | false | false | green | NW8S-Barn · FLEX-6600 · Connected · WT2P |
| Box ticked | Poor (#cc3333) |
true | true | red, outlined AT chip | … · WT2P · Network Poor · Adaptive throttle on |
| Box unticked, link still Poor | Poor (#cc3333) |
false | false | green | … · WT2P (no network grade) |
- Last row: this is the change in
d36fe274. The bridge still publishes the quality (linkQuality: Poor), but with the throttle off the tab doesn't colour its dot by it or mention it. - Whenever the dot is recoloured: the AT chip and the words "Network Poor" are there too.
- Tab width: the tab's on-screen width stayed at 195 px in all three states, and the grab shows the chip with no truncation of "NW8S-Barn".
- Cleanup: after the test the box was unticked, which is how this build found it. The test build was then disconnected and closed.
Not re-run here: an fps cap in force (filled chip). That code path is unchanged since the first proof above, which showed it at 8 fps / Fair.
…nd correct the throttle-cap comment. Principle VIII. Review follow-ups for the AT chip: - unified_title_bar_test probes the dot's centre pixel: plain connected colour without AT, the quality colour with it, plain again once the throttle is off. It also checks that no tab keeps the chip or quality with no session. Each check fails if its gate is removed (verified by mutation). - MainWindow: an applied cap lifts on disconnect, not at the next quality update (RadioModel::applyAdaptiveFrameRate returns early when the box is unticked). - RadioTabBar: tabs that can't show the chip get empty tint/text, and the chip text is built only when a tab can show it. - automation-bridge.md: radios.adaptiveThrottle means the chip is showing, not that the setting is on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@AetherClaude please re-review. Your review was of No issue → #6790. It's filed now and the description says Blocker 1 (premise and visual design): You're right that the old lamp never showed the live network grade, and the description now says so in a correction. It wasn't colour-free either: at The scope of the recolour has also narrowed since you looked. In Nits:
CI gap: the description now says Live proof on a FLEX-6600 through the bridge is in the two comments above. The second one covers |
There was a problem hiding this comment.
Re-review at af06e03b (previous pass was b5c0d1af). Thanks for the correction in the description, the mutation-tested checks and the two bridge proofs — the live tables made several of my attacks cheap to aim.
1. Issue fit
Closes #6790, which is this PR's own spec: it was filed 2026-10-26 06:56Z, 4h49m after the PR opened (02:07Z), by the same author, with no comments and no maintainer input. Against what it asks: item 1 (AT chip, outlined when armed / filled in the cap's colour) and item 3 (grade and throttle state in words) are implemented and restore what #6198 dropped — RadioTabBar.cpp:302-316, RadioTabBar.cpp:477-493, RadioTabBar.cpp:191-201. Item 2 (dot takes the quality colour, only while the chip shows) is implemented at RadioTabBar.cpp:314-316 + RadioTabBar.cpp:864-879, and both the issue and the description say it needs a maintainer ruling. That ruling is the one thing missing, and it's the recommendation below — the code is otherwise the cleanest part of this review.
The premise now checks out: RadioModel::applyAdaptiveFrameRate emits the cap, MainWindow maps it through what is now adaptiveFpsCapColor(), and the pre-#6198 lamp took that same tier colour. Your correction that the lamp never showed the live grade is in the description, so item 2 is correctly labelled as new rather than restored.
2. Scope
| File(s) | What it changes | Claimed in body? | Verdict |
|---|---|---|---|
src/gui/RadioTabBar.{h,cpp} |
AT chip paint/size/state, quality-coloured dot, session gate, 4 new bridge fields + tabs[].throttleChip |
Yes | In scope; new public surface (bridge fields) |
src/gui/MainWindow.{h,cpp} |
refreshAdaptiveThrottleChip(), quality push, lambda → adaptiveFpsCapColor() |
Yes | In scope |
src/gui/MainWindowHelpers.{h,cpp} |
adaptiveFpsCapColor() extracted from the inline lambda |
Yes | In scope; new exported helper |
src/gui/ConnectionPanel.{h,cpp} |
adaptiveThrottleEnabledChanged(bool) signal + emit |
Yes | In scope; new signal (internal API) |
src/gui/TitleBar.cpp |
currentBeatColor() returns invalid instead of fixed green |
Yes (incl. the amber discovery pulse consequence) | In scope |
tests/unified_title_bar_test.cpp |
19 checks: dot pixel, session gate, chip, bridge fields, width | Yes | In scope |
docs/{architecture/window-chrome,automation-bridge,user/docs/menu-reference,user/docs/low-bandwidth-connections}.md |
Documents the above | Yes | In scope |
No unrelated files, no formatting churn, no deleted guard, no CHANGELOG.md entry, and the - lines are confined to the lambda removal and the currentBeatColor() body. The checklist's "No new flat-key AppSettings calls" is accurate: MainWindow.cpp:729-730 reads the existing AdaptiveThrottleEnabled key and writes nothing.
Socket tests: none added, removed or modified. unified_title_bar_test opens no socket; the only network-looking token is the string fixture connected.transport = QStringLiteral("192.168.1.21") at tests/unified_title_bar_test.cpp:466, which is display text.
Security audit (required by the harness, 97 CodeGuard findings): ran, no findings. Every flagged ID is a lexical match with no security meaning — CS-VLN-CMDI-BACKTICK on the backticked identifiers in your new header comments (src/gui/RadioTabBar.h:186), CS-VLN-CMDI-AMP on & as address-of in connect() (src/gui/MainWindow.cpp:2173), CS-VLN-SSRF-PRIV-*/CS-PII-IP on the setPlaceholderText example addresses at src/gui/ConnectionPanel.cpp:2554-2557 (present verbatim on main, never dialled), CS-VLN-PATH-PROC on the pre-existing /proc/self/status RSS read. The new surface is bool/int/QColor moved between widgets: no parser, no sink, no stylesheet, no path. Credit: security-audit (CodeGuard).
3. Blockers
1. GOVERNANCE.md — the quality-coloured dot is a visual-design change with no approved RFC. Not a code defect; a process gate, and you flagged it yourself, so this is mostly me putting the citation on the record for the maintainer.
GOVERNANCE.md:97:
- Any change to visual design — colors, fonts, spacing, theme, icons
and GOVERNANCE.md:122,131:
Open a GitHub issue with the label
rfcand the title prefix[RFC]. […] Do not open a PR until the RFC issue is approved.
plus the normative agent limit at GOVERNANCE.md:221-223:
May not autonomously change: visual design (colours, fonts, layout, theme) […] The Project Maintainer is the sole authority on visual design and UX direction.
#6790 is labelled enhancement/GUI, has no [RFC] prefix, no rfc label, no maintainer comment, and postdates the PR, so it is not the approval that sentence asks for. The GOVERNANCE.md:110-118 exemption for "bug fixes with a clear root cause" plausibly covers items 1 and 3 — the AT chip restores a cue #6198 dropped, and that's a regression with a named root cause. It does not cover item 2: the live-grade palette on the tab dot is new, as your own correction says. Three distinct palette decisions need the ruling, not one: the dot recolour, the chip's filled tint, and the glow now following the dot (amber discovery pulse). What a fix looks like: either relabel #6790 as [RFC] and wait for @ten9876, or split item 2 (RadioTabBar.cpp:314-316, the showQuality gate at RadioTabBar.cpp:874-878, and the TitleBar.cpp:1669-1671 glow change) into a follow-up PR and merge the chip + words now, which stand on the regression exemption alone.
4. Nits (non-blocking)
-
src/gui/MainWindowHelpers.cpp:153-158/src/gui/RadioTabBar.cpp:314-316— I measured the contrast you left unmeasured, and the light theme is the problem. Compositingcolor.titlebar.tab.active.background(#1a0088b0) overcolor.titlebar.background(#ebf5f5f8) gives an effective#ddebf2; dark resolves to#0c2636. Ratios for the dot:Grade light #ddebf2dark #0c2636Excellent/Very Good #00cc661.75:1 7.30:1 Good #00b4d82.02:1 6.33:1 Fair #cc99002.12:1 6.03:1 Poor #cc33334.22:1 3.04:1 replaced color.titlebar.status.connected4.10:1 ( #1a8040)8.48:1 ( #4dd87a)Dark is fine everywhere. In the light theme three of four grades land below
docs/a11y.md:144-146's 3:1 for non-text, and the token they displace passes at 4.10:1 — precisely because the token is theme-switchable and these literals are not. I agree with your nit-4 answer that retokenisingnetworkQualityColor()would drag the status bar in and is out of scope;docs/a11y.md:155explicitly allows that ("concrete hex is a valid intermediate state — file a follow-up issue to add the token"), so the ask is just the follow-up issue, with these numbers in it. The spelled-out grade in the tooltip already covers WCAG 1.4.1; this is 1.4.11. -
docs/automation-bridge.md:3243-3249vssrc/gui/RadioTabBar.cpp:1359— nit 6 isn't fixed; the doc now states something the code doesn't do. The doc saysradios.adaptiveThrottlemeans "the chip is showing", butstate()publishes the ungated flag:{QStringLiteral("adaptiveThrottle"), m_throttleEnabled},m_throttleEnabledis the setting (OR'd with an in-force cap), with no session gate, while the chip additionally requiresours && sessionatRadioTabBar.cpp:874-884. Your own first bridge proof records the divergence: "Disconnect: … the tab showed no chip, whileadaptiveThrottlestayedtrue(the setting itself)." That's the common state — box ticked, nothing connected: fieldtrue, everytabs[].throttleChipfalse. Cheapest fix is the doc, sincetabs[].throttleChipalready carries the per-tab truth: reword to "the throttle is armed for this client (ticked, or a cap still in force after unticking);tabs[].throttleChipsays whether a tab is actually showing the chip." -
src/gui/MainWindow.cpp:7174-7175— the disconnect path clears the cap locally but never pushes it to the bar.m_adaptiveThrottleActive = false; m_adaptiveFpsCap = 0; // clear cap alongside throttle flag — see #2829 review
RadioModel::stopNetworkMonitor()emits onlynetworkQualityChanged(src/models/RadioModel.cpp:8740); the compensatingadaptiveThrottleChanged(false, 0)lives instartNetworkMonitor()(src/models/RadioModel.cpp:8639), i.e. the next connect. So between disconnect and next connectRadioTabBar::m_throttleFpsCap/m_throttleCapColorkeep the dead cap andradios.adaptiveFpsCappublishes it. Reasoned from code, not reproduced: the chip itself stays correctly hidden by the session gate, so this is bridge state only. Drop-in:m_adaptiveThrottleActive = false; m_adaptiveFpsCap = 0; // clear cap alongside throttle flag — see #2829 review refreshAdaptiveThrottleChip();
-
src/gui/TitleBar.h:249— comment invalidated by this PR's own change.QString m_throttleFlashColor; // empty = default green; set while adaptive throttle is activeAfter
TitleBar.cpp:1669-1671, empty means "glow in the dot's own colour", not green. Suggest:// empty = glow in the dot's own colour; set while adaptive throttle is active. -
src/gui/MainWindowHelpers.cpp:153—adaptiveFpsCapColor(0)returns Poor red. Unreachable today (both call sites guardcap > 0, same shape as the lambda it replaced), but it's now an exported helper in a header rather than a lambda two lines from its only caller, so the precondition is worth stating in theMainWindowHelpers.h:66-68comment.
5. What I tried to break (no finding)
- Chip overflowing a narrow tab.
nameWfloors at0.0(RadioTabBar.cpp:467), so I went looking for a squeeze that would push the chip pastkTabPadX. There isn't one:RadioTabBar.cpp:919pinsm_tabHosttom_tabsLayout->sizeHint().width()and the strip scrolls instead of shrinking, andsizeHint()already budgetschipSize().width() + kChipGap(RadioTabBar.cpp:344-347). The floor is unreachable and the chip can't overlap the name or the tab edge. Your 195 px grab agrees. - Per-tick churn on the a11y surface.
applyLinkVisuals()runs on every 80 ms pulse tick, and it now callssetLinkQuality/setThrottleChipon every tab. Both no-change-guard beforerefreshAccessibility()(RadioTabBar.cpp:290-316), andchipTextis built only when a tab can show it, so the steady state is zero tooltip/accessible-name writes and zero repaints.docs/a11y.md's throttled-update rule is satisfied. - A black glow from the
currentBeatColor()change. Returning an invalidQColorcould have painted an invalid gradient;RadioTabBar.cpp:429falls back todot(m_beatColor.isValid() ? m_beatColor : dot), which is the whole point of the change. The amber discovery pulse follows from the same line and is disclosed. - A stale grade surviving a disconnect or following the next radio.
stopNetworkMonitor()emitsnetworkQualityChanged(networkQuality(), 0)withm_netStatealreadyOff(src/models/RadioModel.cpp:8718-8740), which maps to an invalidQColoratMainWindow.cpp:2151-2153;dotColor()additionally requiresRadioTabStatus::Connected, andapplyLinkVisuals()requires a non-emptym_activeId. Three independent gates — I couldn't get a grade onto a tab that isn't the live session's. - Construction order hiding the initial push.
refreshAdaptiveThrottleChip()atMainWindow.cpp:2210is guarded byif (!m_titleBar) return;, which would silently no-op if it ran first. It doesn't:buildUI()runs atMainWindow.cpp:1422and createsm_titleBar(:5469) andm_connPanel(:5527), both before the ctor's connect block. So a ticked box shows the chip from launch, and them_connPanelconnect at:2208isn't dereferencing a null. - Unticking mid-cap.
applyAdaptiveFrameRate()returns early when the setting is off (src/models/RadioModel.cpp:8991-8992), so noadaptiveThrottleChanged(false, 0)ever arrives andm_adaptiveFpsCaplegitimately stays> 0. The corrected comment atMainWindow.cpp:731-733now matches the code; the earlier "next quality update" wording did not. - The "one shared mapping" claim.
NetworkDiagnosticsDialogconnectsadaptiveThrottleChanged(:2363) and was the obvious place for a second cap→colour table.grepfinds only chart-series colours there (:3455,:3473,:3501,:3612) — no sibling tier mapping left behind, so extracting the lambda really did unify every consumer. - The pixel probe. The usual "exact-RGB probes are font-dependent" objection doesn't apply here: the dot is an opaque filled 7 px ellipse at a fixed
kTabPadX, the glow and the active-tab tint both paint under it, and the probe samples the centre. The three-state sequence (plain → quality → plain) plus your mutation counts (4 and 2) is real evidence, and it matches the gates I read atRadioTabBar.cpp:874-884. - CI, honestly. At
af06e03b,build,check-windowsandcheck-macoswere stillin_progress;Static checksandSanitizer option configurespassed. I confirmed your CI-gap disclosure independently:unified_title_bar_testappears in noctest -Rlist in any workflow, so green proves compilation only. Your local run and the bridge proofs are the only evidence these 19 checks pass, and I can't re-run them — everything above is reasoned from the source, not observed at runtime.
6. Recommendation
Needs maintainer decision. The engineering is in good shape: the session gate, the no-change guards, the sizeHint budget and the glow fallback all survived the attacks above, the extracted helper leaves no sibling mapping behind, and the nits are a doc sentence, a stale comment, one missing refresh call and a follow-up issue. What's unresolved is the ruling GOVERNANCE.md requires before the dot recolour, chip tint and glow change can merge — and that's @ten9876's call, not mine. Concrete next step: relabel #6790 as [RFC] with the rfc label and the four RFC headings, including the light-theme contrast numbers above; or, if you'd rather land something now, split item 2 out and merge the chip and the spelled-out words, which the regression exemption covers on their own.
🤖 aethersdr-agent · cost: $10.6860 · model: claude-opus-5
|
Cisco CodeGuard — static analysis of this PR (100 finding(s))
Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them. 🤖 aethersdr-agent · cost: $11.1323 · model: claude-opus-5 |
Closes #6790.
Summary
When the unified title bar landed (#6198), the standalone heartbeat lamp was folded into the active radio tab's status dot. Before that, the lamp flashed in the cap's colour while an adaptive-throttle cap was in force: red at 4 fps, amber at 8, blue above that, the same tiers the status bar uses for quality. Since #6198 the throttle shows only as a faint tint on the dot's heartbeat glow, and nothing shows that the throttle is armed but not capping.
Correction: an earlier version of this description said the lamp showed the live network quality. It didn't: it showed the cap's tier, and only while a cap was in force. Showing the live grade on the dot (below) is new. It is a visual-design change that needs a maintainer ruling, which #6790 asks for.
This PR, on the connected radio's tab:
adaptiveFpsCapColor(), moved out of an inline lambda inMainWindow.cppso the glow tint and the chip share it). The chip follows the checkbox immediately, through a newConnectionPanel::adaptiveThrottleEnabledChangedsignal. Unticking the box doesn't lift a cap already in force (it lifts on disconnect), so the chip stays filled until then.networkQualityColor(), the same mapping as the status bar: green for Excellent or Very Good, blue for Good, amber for Fair and red for Poor. With the throttle off, the dot stays the plain green "connected" colour. A recoloured dot therefore always comes with the chip that explains it (operator feedback,d36fe274). Discovery amber and link-lost red still take priority.TitleBar::currentBeatColor()returns no colour, so the glow takes the dot's colour instead of a fixed green. This also changes the discovery pulse: over an amber discovery dot it now glows amber rather than green.titlebar→radios). New fields:linkQuality,linkQualityColor,adaptiveThrottle,adaptiveFpsCap, andtabs[].throttleChip.Docs updated:
menu-reference.md(Radio tabs),low-bandwidth-connections.md,architecture/window-chrome.md,automation-bridge.md.Intent
After this merges, an operator can read the link quality and whether adaptive throttle is armed or capping straight from the radio tab, as they could from the old lamp. Out of scope: any change to the quality thresholds, the throttle policy, or the status-bar network label.
Constitution principle honored
Principle VIII — Evidence Over Assertion. The new behaviour is asserted in
unified_title_bar_testand exposed as bridge state, so it can be checked as data rather than from a screenshot.Test plan
unified_title_bar_testbuilt and run locally (Windows, Qt 6.12, offscreen). The new checks all pass:adaptiveFpsCap, and clears; the tab returns to its plain width.af06e03b): with&& m_throttleEnableddropped from the quality gate, four of these checks fail; with&& !m_activeId.isEmpty()dropped from the session gate, two fail.unified_title_bar_testis not inci.yml's per-PRctest -Rlist, so green CI proves it compiles, not that it passes. The local run above is the only evidence for these checks.color.titlebar.tab.active.backgroundin the bundled themes (docs/a11y.mdasks for 3:1). The colours come fromnetworkQualityColor(), as in the status bar, and are not theme tokens.four radios fit whole in an 1800 px barfails locally with and without this change (checked by stashing the change). CI is the arbiter.d36fe274(comment): with the link Poor and the throttle off, the dot is plain green and the tooltip names no grade. Ticking the box shows the AT chip, a red dot and "Network Poor".Checklist
AppSettingscalls (reads the existingAdaptiveThrottleEnabledkey)CHANGELOG.mduntouched🤖 Generated with Claude Code