Repository navigation
fix(a11y): status-bar indicators work from the keyboard and read as buttons. Principle VI. - #6267
Conversation
…uttons. Principle VIII. Closes #6257. The ten click-handled status-bar indicators (band stack, +PAN, TNF, CWX, ASR, DVK, FDX, TUN, AMP and TX) were QLabels and label containers that only a mouse could use, the interactive-QLabel anti-pattern in docs/a11y.md. A screen-reader user could not open the DVK panel, cancel transmit or switch full duplex from the status bar. - New StatusIndicator helper (src/gui/StatusIndicator.{h,cpp}), attached to each indicator: - Tab reaches it (Qt::TabFocus, so a mouse click still takes no focus). - Return, Enter or Space runs its action. - An accessibility factory reports it as a Button with a Press action that runs the same action. - A 2 px color.canon.cyan underline shows while it has keyboard focus. It is a child bar rather than a stylesheet rule, because the indicators restyle themselves with whole-sheet setStyleSheet() calls. - The indicators keep their existing look and layout. - MainWindow::activateStatusIndicator() holds the ten actions, moved verbatim from the eventFilter's press branches. A mouse press and the keyboard now run one path. TX still answers only the left button to a mouse; the keyboard path is the left-click equivalent. - Band stack, TNF, CWX, ASR and FDX gain accessible names ("Band stack panel", "Tracking notch filters", "CW keyer", "Speech-to-text (Copy Assist)", "Full duplex"); the other five already had them. Maintainer rulings: all ten indicators; keep QLabels and add focus and keys rather than converting them to QPushButtons; a cyan underline as the focus mark. Tests: status_indicator_test (16 cases, offscreen): - Tab focus policy - Return/Enter/Space activate; other keys and a disabled indicator do not - Button role, accessible name and Press action, and the action activates - a label container activates the same way, and its inner labels stay plain text - the underline shows on keyboard focus along the bottom edge, hides on focus out, and never takes focus or clicks In the running app (offscreen, isolated profile, no radio), the widget tree shows all ten indicators with their names, and a click on the band stack toggles its panel open, closed and open through the shared action path. The full ctest suite passes (673/673); check_a11y reports nothing; the gates pass; the colour ratchet is flat. The touchpoint manifest records one more ThemeManager.h includer (StatusIndicator.cpp, for the underline token). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
|
Antares Detector — candidate vulnerable file(s)
The indicatorPosition implementation in src/gui/SmartMtrConfig.cpp calls meterConfig(in.kind).valueToPosition(...) and clamps the result, but the valueToPosition implementation is not shown here and may perform unsafe array indexing/scaling on untrusted input. This can lead to out-of-bounds access or unsafe scaling if valueToPosition uses direct indexing or assumes valid min/max ranges. 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: $6.8560 · model: claude-opus-5 |
ten9876
left a comment
There was a problem hiding this comment.
Issue fit: partially fixes #6257; recommendation: Request changes. The ten indicators receive focus, key handling and a shared activation path, matching the issue and the maintainer rulings disclosed in the body. However, ASR-disabled builds fail, and starting with accessibility active leaves the original accessibility interfaces cached, so the screen-reader activation requirement is still broken.
Scope audit
| File/group | Change | Claimed? | Verdict |
|---|---|---|---|
src/gui/MainWindow.cpp |
Attach helpers to ten indicators; add missing names | Yes | In scope |
src/gui/MainWindow.h, MainWindow_Shortcuts.cpp |
Share mouse/keyboard actions and recognize indicators | Yes | In scope; blocker 1 |
src/gui/StatusIndicator.{h,cpp} |
Focus, key handling, Button/Press interface, token-based underline | Yes | In scope; blocker 2 |
CMakeLists.txt |
Compile the helper into the GUI | Yes | Necessary wiring |
tests/status_indicator_test.cpp, tests/tests.cmake |
Register socket-free helper coverage | Yes | In scope; misses active-at-construction accessibility |
docs/architecture/aetherd-touchpoints.md |
ThemeManager includers 167 → 168 | Explicitly disclosed | In scope |
Everything in the diff is explained by the issue. One signed commit, with no unrelated commit history, new setting/dependency/default, or CHANGELOG entry. GitHub verifies the signature. The maintainer's explicit ten-indicator/helper/underline rulings address the UX/design choices.
Blockers
-
[P1] ASR-disabled configurations do not compile —
src/gui/MainWindow_Shortcuts.cpp:869.isStatusIndicator()compares againstm_asrIndicatorunconditionally, butMainWindow.h:1738declares that member only underAETHER_ASR_ENABLED. The Windows job configuresENABLE_ASR=OFF. I independently obtained its compiler output, rather than relying on the existing bot's inference:MainWindow_Shortcuts.cpp(869): error C2065: 'm_asrIndicator': undeclared identifierFailing required Windows job. This affects every platform building with ASR off. Keep the conditional member comparison under the same guard:
bool MainWindow::isStatusIndicator(const QObject* obj) const { if (!obj) { return false; } #ifdef AETHER_ASR_ENABLED if (obj == m_asrIndicator) { return true; } #endif return obj == m_cwxIndicator || obj == m_tnfIndicator || obj == m_fdxIndicator || obj == m_bandStackIndicator || obj == m_tgxlContainer || obj == m_pgxlContainer || obj == m_txIndicator || obj == m_addPanLabel || obj == m_dvkIndicator; }
-
[P1] Active accessibility caches the original interface before attachment —
src/gui/StatusIndicator.cpp:69; construction order atsrc/gui/MainWindow.cpp:5805, coverage attests/status_indicator_test.cpp:97.The app sets accessible names before
keyboardOperable()attaches the helper. With accessibility already active, Qt resolves and caches the original interface during these change notifications. Installing a factory and adding the helper property does not replace a cached interface. Subsequent queries return the original StaticText interface for a label, without a Press action; containers also retain their original role.Reproduced on the pinned Qt 6.12, using the PR's actual helper, without a custom accessibility handler:
active=1 name-before-attach role=41 expected=43 cached label: before_role=41 after_role=41 expected_button=43 same_interface=1 press=041is StaticText and43is Button. The unmodified test passes 16/16. Moving its existingQAccessible::setActive(true)before widget construction produces 12/16, failing Button role, Press action availability/activation, and container Button role. No native screen-reader speech was measured: this is a deterministic reproduction at the Qt interface consumed by assistive technology.Qt's implementation explicitly resolves interfaces during active accessibility updates and consults the cache before factories: Qt accessibility implementation. Invalidate the previous interface after attaching the helper; include an active-before-construction regression. This replacement for the return at line 69 restored 16/16 in the adversarial variant:
StatusIndicator* helper = new StatusIndicator(widget); if (QAccessibleInterface* iface = QAccessible::queryAccessibleInterface(widget)) { QAccessible::deleteAccessibleInterface(QAccessible::uniqueId(iface)); } return helper;
Also verify platform accessibility notifications when replacing an interface that an AT client has already observed.
Non-blocking nit
src/gui/StatusIndicator.cpp:117: repeated KeyPress events activate repeatedly. One Space press followed by oneisAutoRepeat()event emitted two activations. Consider consuming auto-repeat without activating for these toggle/cycle controls, and cover it with a socket-free test. Holding Space can otherwise repeatedly toggle a panel or cycle TUN/AMP state.
Verification and limits
The new test owns no sockets and contains no synthetic device peer. The original 16 checks passed; removing key activation made five checks fail (11/16), demonstrating meaningful keyboard coverage. Disabled activation, unrelated keys, inner-label roles and underline geometry/focus behavior survived the existing tests. Active-before-construction and cached-interface probes exposed blocker 2; the cache-invalidation variant restored all 16 checks.
Engine boundary remains 102 known findings/zero blockers, command plane remains 368 commands across 32 files, capability records/test registration/frozen CI gate/QSettings checks pass, and a11y lint reports zero findings on all five touched GUI files. Colour counts match base exactly: 582 unique colours, 2487 references, 893 stylesheet calls. No automated code-review skill was available; the existing bot's ASR diagnosis was independently verified. Its unrelated SmartMtr speculation supplies no evidence about this diff and is not adopted.
I independently configured and built AetherSDR and the registered status_indicator_test from the clean PR head e2c62cd98 in /tmp/pr6267-build; the registered test passed (1/1). A real two-indicator widget chain also passed Tab navigation. The earlier adversarial binaries compiled the actual head helper/test against unchanged base support libraries; the subsequent full head build independently passed the original test.
Two isolated offscreen head instances used fresh settings, explicit sockets and AETHER_AUTOMATION_NO_TX=1. Bridge ping reported sha=e2c62cd98, dirty=false; connect local serial DEMO-0001, connect wait, and get radio confirmed the demo, one pan/one slice and transmitting=false. An owned LD_PRELOAD Qt-entrypoint probe in the first instance queried all ten Button/Press interfaces and sent Space/accessible Press into the actual app:
REVIEW keyboard_space panel_before=0 panel_after=1
REVIEW keyboard_space panel_before=1 panel_after=0
REVIEW keyboard_space panel_before=0 panel_after=1
REVIEW accessible_press panel_before=1 panel_after=0
The second instance ran without instrumentation. Bridge clickAt on Band stack panel plus dumpTree returned panel visibility [true], [false], [true], confirming the mouse path still works. Generic invoke click does not support QLabel; coordinate clicks do. Both owned instances (pr6267-head-review, pr6267-mouse-review) were closed through their explicit sockets and exited successfully; the existing operator instance was untouched. I did not build/run a base app; the base's NoFocus/StaticText behavior was inspected, and deleting key activation produced the expected failing behavioral tests.
CI tested a pull_request merge created 2026-10-07 23:55:11 UTC after main's current tip (23:35:09 UTC), so it is not stale against current main. Linux/macOS/static checks pass; required Windows check fails. The author log records 673 selected tests with one skipped; I did not independently rerun the full suite or a native screen reader.
Recommendation: Request changes. Fix both blockers, add the active-at-construction regression and rerun the required Windows job. This review is submitted as COMMENT because the authenticated account (ten9876) is also the PR author, which GitHub does not permit to request changes on its own PR.
…n reader already cached, and ignore key auto-repeat. Principle VIII. Addresses the review on #6267 (#6257). - isStatusIndicator() compares m_asrIndicator only under AETHER_ASR_ENABLED, where the member is declared. check-windows builds with ENABLE_ASR=OFF, and the unguarded comparison failed there with C2065. With ASR off, MainWindow.cpp and MainWindow_Shortcuts.cpp now compile; restoring the old line reproduces "'m_asrIndicator' was not declared in this scope". - With accessibility already active, setAccessibleName() makes Qt create and cache the widget's plain interface before attach() runs, and Qt consults that cache before any factory. attach() now drops a cached non-Button interface, so the next query builds the Button with its Press action. It also sends ObjectCreated, so an assistive client that already saw the old object looks again. - Return/Enter/Space auto-repeat is consumed without acting. These indicators toggle panels and cycle TUN/AMP, so holding a key no longer repeats the action. status_indicator_test 24/24. New cases: widgets built and named after accessibility is active (label and container) become Buttons with a working Press action, keep their name, and announce ObjectCreated once each; a held Space is consumed without a second activation. Without the cache replacement 5 checks fail; without the auto-repeat guard the held-key check fails. Full ctest passes (673/673); check_a11y and the gates pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
All addressed in Review blockers1. ASR-off builds don't compile. Fixed with your guard, at
2. A cached interface survives
With the replacement removed, 5 checks fail (19/24). NitAuto-repeat. Return/Enter/Space auto-repeat is consumed without acting, so holding a key no longer re-toggles a panel or cycles TUN/AMP. A held-Space event is consumed with no second activation. With the guard removed, that check fails. Bot comments
Verification
|
There was a problem hiding this comment.
Issue fit
Partially. #6257 asks for Qt::TabFocus, activation with Return or Space, and a Button role on the click-only status-bar indicators. Tab focus, Return/Enter, the Button role and the accessible Press action all land, and the StatusIndicator helper is a genuinely good shape — one action path shared by mouse and keyboard, a child focus bar that sidesteps the whole-sheet setStyleSheet() problem, and the DVK panel finally has a non-mouse entry point, which was the worst part of the issue. Space does not work in the app, for a reason the helper can't see from where it sits (Blocker 1), and the scope expansion past the five styled indicators rests on rulings I can't find (Blocker 2).
Scope
| File / group | What it changes | Claimed by title or body? | Verdict |
|---|---|---|---|
src/gui/StatusIndicator.{h,cpp} |
New helper: focus policy, key activation, Button role + Press, focus underline | Yes | In scope |
src/gui/MainWindow.cpp (buildUI) |
keyboardOperable() lambda on ten indicators; five new accessible names |
Yes | In scope |
src/gui/MainWindow_Shortcuts.cpp |
Press branches extracted to isStatusIndicator / activateStatusIndicator |
Yes | In scope |
src/gui/MainWindow.h |
Two slot declarations | Yes | In scope |
CMakeLists.txt |
GUI_SOURCES += StatusIndicator.cpp |
Implied | In scope — required, the file would otherwise be a dead addition |
tests/status_indicator_test.cpp, tests/tests.cmake |
New registered test, matches the dvk_panel_theme_test block above it verbatim |
Yes | In scope |
docs/architecture/aetherd-touchpoints.md |
core/ThemeManager.h includers 167 → 168 |
Yes, disclosed explicitly | In scope |
No CHANGELOG entry, no new settings keys, no new public/protocol surface, no deleted guards. The scope expansion from five indicators to ten is a scope question but it's disclosed in the body, so it's Blocker 2 rather than an undisclosed row here.
Blockers
1. src/gui/StatusIndicator.cpp:124-133 — Space never reaches the helper when a radio is connected; it keys the transmitter instead.
if (key == Qt::Key_Return || key == Qt::Key_Enter || key == Qt::Key_Space) {MainWindow is installed as an application-level event filter (src/gui/MainWindow.cpp:1547, qApp->installEventFilter(this)). Qt runs application filters before the receiving object's own filters (QCoreApplication::notify → sendThroughApplicationEventFilters → sendThroughObjectEventFilters), so MainWindow::eventFilter sees every KeyPress before StatusIndicator::eventFilter does. That filter calls handlePttHoldShortcut on every key press (src/gui/MainWindow_Shortcuts.cpp:735), and ptt_hold is bound to Space by default (src/gui/MainWindow_Shortcuts.cpp:1229, QKeySequence(Qt::Key_Space), keysTx=true).
With a focused indicator, pttHoldKeyStep(KeyPress, holdActive=false, shortcutsEnabled=true, textEntryCaptured=false, connected=true) returns KeyTx — a focused QLabel is not text entry — and the branch returns true. src/gui/PttHoldKeyStep.h says so in its own comment: "Consume the bound key so it cannot also activate a button."
So in the primary operating state (connected, shortcuts on), Tab to FDX, CWX, "Cancel transmit" or the AMP stack and press Space → MOX keys for as long as the key is held, and the indicator does not activate. Disconnected, or with shortcuts off, pttHoldKeyStep returns PassThrough and Space works — which is exactly the state the body's one instrumented run was in ("offscreen, isolated profile, no radio"). The body lists keyboard activation in the running app under "Not verified", so this is the gap that check would have closed.
tests/status_indicator_test.cpp:95 (report("Return, Enter and Space each activate it", dvkActivations == 3)) can't see it either: the test installs no application-level filter, so the assertion is true of the helper in isolation and false of the helper in the app. Space is the key assistive technology presses on something advertised as a Button, so a screen-reader user tabbing to the DVK indicator and pressing Space keys the radio rather than opening the panel.
This is also precisely what #6257's triage comment asked to be ruled on — "Return vs Space — needs a decision".
Smallest honest fix: drop Space from the helper and say so in the body. Return/Enter plus the accessible Press action still satisfy the issue, and #6257's ask for Space becomes a maintainer call about whether a focused indicator should steal Space from PTT:
if (key == Qt::Key_Return || key == Qt::Key_Enter) {Please don't route the helper through ShortcutManager to resolve this — that couples a generic widget helper to MainWindow. If the maintainer does want Space, it belongs in the app-level filter ahead of handlePttHoldShortcut, and that is a TX-behaviour change deserving its own ruling. Either way there is a deterministic, policy-compliant pin available: tests/window_shortcut_test.cpp:272 already drives ptt_hold through a filter, so "focused helper + connected + shortcuts on → Space does not activate, Return does" is testable at that seam without a fake radio.
2. src/gui/MainWindow.cpp:5743-6207 — the three "maintainer rulings" the scope rests on aren't in either thread.
The body states: "Maintainer rulings: all ten indicators; keep them as QLabels and add focus and keys, rather than converting them to buttons; a cyan underline as the focus mark." I read every comment on #6257 and on this PR. #6257 has one comment, from aethersdr-agent[bot], and it recommends the opposite on scope:
I'd hold this issue to the five indicators that genuinely share the
greyIndLg/setIndicatorStylepath. … the TGXL/PGXL containers cycle an amplifier into OPERATE, so giving them a focusable Space target deserves its own look.m_txIndicatoris … a different widget with different semantics; separate issue.
#6257 still carries the maintainer-review label. I'm not calling the claim false — the ruling may have arrived somewhere I can't see — but it's unverifiable from the record, and it reverses a documented recommendation. Two of the five added indicators are the ones that comment carved out: m_tgxlContainer (:6134) and m_pgxlContainer (:6172). Tab + a keystroke now cycles a tuner through BYPASS/STANDBY and puts an amplifier into OPERATE, which is the "own look" that was asked for. Routing this to Needs maintainer decision on scope: either link the ruling, or split TUN/AMP (and arguably TX) into a follow-up and land the eight uncontested ones.
Worth noting the keyboard reach for TUN/AMP intersects Blocker 1: with Space taken by PTT, those two cycle on Return/Enter only today.
Nits
tests/status_indicator_test.cpp:110-113—report("a disabled indicator does not activate", ...)callsdvkHelper->activate()directly rather than sending a key to the disabled widget, so it pins theisEnabled()guard insideactivate()but not the end-to-end disabled path. Cheap to strengthen:press(dvk, Qt::Key_Return)while disabled, alongside the direct call.src/gui/MainWindow.cpp:10489—m_dvkIndicator->setEnabled(dvkAvailable). Qt skips disabled widgets in the tab chain, so theaccessibleDescriptioncarrying the live unavailability reason (#6247,:10508) is unreachable to a keyboard-only screen-reader user, even thoughdocs/a11y.md:194-213makes that description the channel the reason must travel on. Tree-browsing AT still finds it, so this is not a regression this PR introduces — but "dimmed with a reason" and "focusable" are in tension and the PR is the first thing to make it visible. Out of scope to fix here; worth a follow-up note.- The body cites
check_a11yreporting nothing on the touched files as evidence. It can't report anything here: as #6257's own triage established, Check 1 builds its label set per-file from local declarations and looks within ±6 lines of theeventFiltertoken, and these indicators are header-declared and handled in a different translation unit. A clean run is vacuous for this change, not confirmation. src/gui/StatusIndicator.h:12-15— the class comment promises "Return/Enter/Space activate it". Needs the same correction as the body if Blocker 1 lands as suggested.
What I tried to break
- The refactor dropping a guard. Walked all ten extracted branches against their
-lines: everyif (!x->isEnabled()) return true;becamereturninsideactivateStatusIndicatorwith the filter still returningtrue, so consumption is unchanged; the TX left-button-only gate survived at the filter (MainWindow_Shortcuts.cpp:856-859) and is behaviourally identical to the old inner check. FDX's command-then-apply-on-reply shape moved verbatim. No guard lost. - The ASR-off lane.
isStatusIndicatorandactivateStatusIndicatorboth guardm_asrIndicatorunderAETHER_ASR_ENABLED, as doeskeyboardOperable(m_asrIndicator). The member set inisStatusIndicatormatches the ten old press branches exactly — nothing added, nothing dropped.check-windows(the only-DENABLE_ASR=OFFlane) is green on55990b5, as arebuild,check-macos, Static checks, Sanitizer configures and both CodeQL jobs. - Stylesheet cascade eating the focus bar. The indicators repolish with whole-sheet
setStyleSheet(), and Qt sheets cascade to children — but every sheet in that path isQLabel {-selected (MainWindow.cpp:5657-5660,10396-10398) and the focus bar is a bareQWidget, so none of them match it. The TUN/AMP containers set no sheet at all. The child-bar approach holds. - Lifetime and dangling state.
StatusIndicatorisQObject(widget)-parented, so theaetherStatusIndicatorproperty can never outlive its target; theMainWindowconnect takesthisas context and the sender dies with the indicator, so the lambda can't fire into a dead widget;doActionnull-checksof(widget()).attach()is idempotent via the earlyof()return, andinstallFactoryruns once behind a function-local static. - Inner labels of a container turning into buttons. The factory keys on the helper property, which only the container carries, so TUN/OPERATE stay
StaticText— the test pins this at:131-133and the mechanism agrees. - Theme token and test registration.
color.canon.cyanresolves in both themes (#005f85light,#5de3ffdark) — I walked the JSON rather than grepping, since those tokens are nested. Thetests.cmakeblock mirrors thedvk_panel_theme_testregistration above it (same link set,THEME_TEST_RESOURCES, offscreen,TIMEOUT 60), andStatusIndicator.cppis referenced from bothGUI_SOURCESand the test target, so neither is a dead addition. - Socket tests: none added, modified or removed by this PR.
- Could not check: no build and no GUI here, so the focus bar's painting and placement inside the TUN/AMP containers (its child is created before their
QVBoxLayoutand is never laid out), real AT behaviour, and whether NVDA/Orca/JAWS actually re-query on theObjectCreatedthe interface replacement sends. The body already flags the last of those honestly. Every finding above is reasoned from the code in the head checkout, not reproduced at runtime — including Blocker 1, which rests on Qt's documented filter ordering plus three source sites rather than an observed keystroke.
Recommendation
Request changes, on Blocker 1 alone — the advertised key does the wrong thing in the state that matters, and what it does instead is key the transmitter. The fix is one line plus a body correction, and the mechanism is a seam the helper genuinely couldn't see from inside itself; a pin at window_shortcut_test.cpp would keep it from coming back. Blocker 2 is for the maintainer, not for you: link the ruling and it evaporates, or split TUN/AMP out and the remaining eight are ready.
Everything else here is solid work. One action path for mouse and keyboard is the right shape, the cached-interface replacement in attach() is a subtlety most implementations miss, and the invert-each-fix test discipline is exactly what Principle VIII is asking for. Ten indicators is real work — thank you for taking the interactive-QLabel cleanup on rather than doing just the DVK one.
🤖 aethersdr-agent · cost: $5.6748 · model: claude-opus-5
…tire a replaced interface. Principle VI. Landing pass on #6267 (#6257). Space (maintainer: Return/Enter only) - MainWindow's app-level filter runs before any widget filter and binds Space to PTT (Hold), so with a radio connected and shortcuts on, Space on a focused indicator keyed MOX instead of activating it, even on "Cancel transmit". The helper now takes Return/Enter only and leaves Space untouched, so Space means PTT everywhere, as for every other button. The class comment and description say so. - status_indicator_test reproduces MainWindow's PTT decision as an app-level filter (the real pttHoldKeyStep(), connected, shortcuts on). Behind it, Space on a focused indicator is PTT and does not activate it; Return still activates it and keys nothing. On/off state (maintainer: fold in) - TNF, FDX, CWX, ASR, DVK and the band stack are checkable buttons: StatusIndicator::setCheckable/setChecked report QAccessible checkable and checked, and emit one StateChanged per real change. MainWindow sets the state wherever it restyles them: TNF global enable, full duplex, an open CWX/DVK/ASR panel, and the band stack's visibility. Colour is no longer the only carrier of the state. - TUN and AMP lead their accessible description with the current state word ("OPERATE. Press to cycle between OPERATE, BYPASS, and STANDBY"). Smaller - Replacing a cached interface first sends ObjectDestroyed for the old object, then ObjectCreated for the Button, so a client holding the old id lets it go. - The accessibility factory acts only at an object's most-derived class level, so every other object and level skips the property lookup. - The disabled-indicator test sends a real Return key as well as calling activate(). Maintainer rulings in this pass: keep all ten indicators (recorded on #6257); keep the shared mouse/keyboard dispatcher; leave disabled indicators out of the Tab chain, as Qt does; accept core/ThemeManager.h's one new includer (StatusIndicator.cpp, for the underline token). status_indicator_test 33/33. Each guard was checked by inverting it: - Space back in the helper: 5 fail - checked-state reporting removed: 2 fail - StateChanged removed: 3 fail Full ctest 673/673. ASR-off compiles. Demo bridge session (DEMO-0001, NO_TX): transmitting=false, the indicator names are present, and a band stack click opens its panel. Gates match the merge base apart from the two new files; the colour ratchet is flat. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
All addressed in aethersdr-agent reviewBlocker 1: Space keys TX. Confirmed and fixed.
Blocker 2: rulings not on record. The rulings were given to the agent in session. They are now recorded on #6257, and the maintainer confirmed keeping all ten, TUN/AMP/TX included. Those three are keyboard-operable on Return/Enter and the Press action. Nits:
Automated code-review pass
Verification
|
…merged together. Principle VIII. Each PR regenerated the manifest without the other. Together, ThemeManager.h has one more GUI includer (168 to 169), which left Static checks red on main. The tag is unchanged (ui-support); only the count moves. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ouchpoint manifest is regenerated after #6267 and #6273. Principle VIII. (#6275) Fixes main after #6273. On `8603a1a1`, **Build CI Docker Image** and **System Libraries Canary** fail on the libsecret check that #6273 added. ## Cause qtkeychain loads libsecret at runtime: `LibSecretKeyring` loads `secret-1` through `QLibrary`. Ubuntu links with `--as-needed` by default, so the built library has no `NEEDED libsecret-1` entry, even though its libsecret backend is compiled in. The check looked for that entry (`readelf -d … | grep libsecret-1`), so it rejected a correct build. The same check is in `setup-qtkeychain.sh`, which is also used by Ubuntu builds that follow the README and by the AppImage job. ## Fix Both checks now look for the backend itself: the libsecret function names qtkeychain resolves (`secret_password_lookup` …), which are in the library only when it was built with `LIBSECRET_SUPPORT=ON`. ## Evidence - `setup-qtkeychain.sh` built with `LDFLAGS=-Wl,--as-needed`, reproducing Ubuntu's linking: - `NEEDED libsecret: 0`, so the old check would fail; - the script prints `Verified: qtkeychain has its libsecret backend`. - A `LIBSECRET_SUPPORT=OFF` build (qtkeychain 0.16.0) has no `secret_password_lookup`, so the new check fails it as it should. - The script header no longer claims linuxdeploy bundles libsecret. It is loaded from the user's system, which every Secret Service desktop provides. ## Also: stale touchpoint manifest #6267 and #6273 each regenerated `docs/architecture/aetherd-touchpoints.md` without the other's change. Together, `core/ThemeManager.h` has one more GUI includer (168 to 169), which leaves **Static checks** red on main. The manifest is regenerated here (`gen_touchpoint_manifest.py --check`: up to date). The tag is unchanged (ui-support); only the count moves. The Dockerfile change takes effect when the CI image is next built: **Build CI Docker Image** runs on push to main and proves it there. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… Principle VIII. An audit of every page against the 106 PRs in v26.10.2, each fact checked against the source at the release commit: - Menu paths: AetherMap, AetherModem, Callsign Lookup and FreeDV Reporter are on View, and Waveforms is on Tools. - New coverage: Windows Firewall rules and the Network Diagnostics page (#6289), live CW contacts (#6268), NB2 on the HL2 and ANAN-G2 (#5966), ANAN-G2 AGC-T and per-radio memory (#6282, #6129), HL2 state restore and its level changes (#6259, #6254, #5975), DVK import (#6247, #6262, #6251), Copy Assist from unprocessed audio (#6216), keyboard-operable status-bar indicators (#6267), the KPA1500 (#5905), CTR2 extensions and the AetherKnob (#6338), live PPM (#6082), and Icom COMP and the squelch scale (#6174, #6184). - Known issues whose GitHub issues are closed are removed. - The style guide now asks for US spelling, and every page follows it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… and re-take the Stable snapshot. Principle VIII. (#6396) ## Summary This brings the user guide (docs.aethersdr.com) up to date with everything in v26.10.2 and re-takes the Stable snapshot so the default site shows it. Docs only, apart from the TCI page prose in `tools/docs/gen_reference.py`; no application source, test or CI changes. Every page was audited against the 106 PRs in the release. Every fact that was added or kept was checked against the source at `f753dbfc4`, the v26.10.2 release commit, not taken from the PR bodies alone. ## What changed - **Menu paths.** - AetherModem, Callsign Lookup, AetherMap and FreeDV Reporter are on **Tools**. They are created on View and then moved to Tools (`MainWindow_Menus.cpp:1519-1520`, `:1569-1575`); the style guide's example says **Tools → AetherMap...**. - **Show live CW contacts** is on **View**. - Waveforms is on **Tools**, not File. - The VUDU applet button was called TXDSP. - Labels now match the source's `...` vs `…`. - **Gaps from this cycle.** - **Windows and Network Diagnostics:** Windows Firewall rules and the Network Diagnostics Windows Firewall page (#6289); the `aether.network` and `aether.nvafx` log categories (#6289, #6308). - **Remote access:** Tailscale shim revocation semantics (#6325). - **Sleep, App Nap and power throttling (#6195):** per-OS notes. - **Hermes-Lite 2:** state restore, RX audio at unity, CW at TUNE level, multi-interface discovery and a discovery troubleshooting entry (#6259, #6254, #5975, #6009, #6293). - **ANAN-G2:** AGC-T range, a "What is remembered" section, and supply voltage and link status (#6282, #6129, #6137, #6211). - **NB2** on the HL2 and ANAN-G2 (#5966). - **DVK:** WAV import requirements, the slot menu without Delete, status text and licensing (#6247, #6262, #6251). - **Copy Assist** from unprocessed audio (#6216). - **Live CW contacts** replace the decoder's contact card (#6268). - **Status-bar indicators** work from the keyboard (#6267). - **Volume:** controllers, hotkeys and TCI `volume` follow the active output (#6129, #6260). - **Peripherals:** the KPA1500 (#5905), CTR2 link extensions and the AetherKnob (#6338), TMate 2 udev (#6250), MIDI RADE Modem and FDV modes (#6031, #6038). - **RTL-SDR:** live PPM (#6082). - **Icom:** COMP and the MK2 squelch scale (#6174, #6184). - **Known issues.** Entries whose GitHub issues are closed are removed (each checked with `gh issue view`). As decided, this includes three closed today without a demonstrated fix: #5107, #4824 and #5665. - **US spelling.** `docs-style-guide.md` asked for British spelling; it now asks for US, by the maintainer's decision, and every page is converted. The generated TCI pages are converted at their source, the prose strings in `tools/docs/gen_reference.py`. Code spans, links and URLs were left alone. UI labels were checked against the source strings, and none of them are British. Two in-page anchors and two Log Analyzer rule anchors (`#bnr-is-grayed-out-…`) follow the renamed headings. - **Stable snapshot.** `snapshot_stable.py 26.10.2` was re-run on the release tree with these corrections, before `main` was merged in, so Stable carries no post-release docs. `docs/` also has `main`'s post-release pages, in US spelling. Stable alone keeps the IC-9700 waterfall-rate entry (#5996): #6402 fixed it after the 26.10.2 tag, so 26.10.2 still has it. The PDF manual already attached to the v26.10.2 release keeps the tag's docs. ## Validation - `npm ci && npm run build` in `docs/user`: success. This is the broken-link and broken-anchor check. - `node tools/docs/test_log_rules.mjs`: 707 checks passed for 45 rules. - `python3 tools/docs/gen_reference.py --check`: up to date (8 pages). - `git diff --check`: clean. - Menu paths on a build of this branch (offscreen, isolated settings, no radio), `menu list`: `Tools → AetherModem...`, `Tools → Callsign Lookup...`, `Tools → AetherMap...`, `Tools → FreeDV Reporter...`, `Tools → Waveforms...`, `View → Show live CW contacts`. ## Screenshots now out of date (not recaptured) These need a recapture pass on the radio: - **Old panadapter title strip (#6386):** `panadapter`, `main-window`, `first-session-*`, `multi-slice-two-slices`, `split-pair`, `tnf-markers`, `offscreen-vfo-indicator`, `pan-3d-stacked-trace`, `default-light-theme`. - **Pre-canon windows (#6334, #6339, #6341, #6372):** `aetherrx*`, `aethertx*`, `aethermodem-aprs`, `bnr-panel`, `connect-dialog`, `connect-by-ip-card`, `connect-icom-rows`, `connect-smartlink-card`, `rx-agc-calibration`. - **Other:** - `title-bar` (lowercase tab states, #6281); - `menu-help` (#6294); - `display-panel` (#6371); - `support-diagnostics-dialog`; - `waveforms-dialog` and `tailnet-remote-access` (show shim 0.4.0); - `about-window` (v26.10.1); - `radio-setup-qrz`. ## Found during the audit, not fixed here - **App string bug:** `TailnetShimDialog.cpp:628` tells the user to "Connect remotely with Connect → Manual"; the real path is **File → Connect to Radio... → Connect by IP**. - **ANAN AGC-T tooltips:** the RX applet and VFO tooltips show the slider position as dB, which is wrong on ANAN. #6282 lists this as a follow-up. - **MIDI Master Volume on non-Flex radios:** it is refused while PC Audio is off, whereas hotkeys, FlexControl and HID drive the line out. The docs describe the code as it is. - **RTL mode names:** `rtl-sdr.md` writes "FM-N" and "CW-R", but the source strings are `FMN` and `CWR`. Left as is until someone confirms what the mode menu displays. - **IC-705 COMP readback:** inferred from the MK2 (#6174) and not verified on an IC-705. - **Not yet documented:** the #6230 Radio Health row "Forward responses skipped", and the Store/MSIX package. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
Closes #6257. Related to #4896.
The ten click-handled status-bar indicators (band stack, +PAN, TNF, CWX, ASR, DVK, FDX, TUN, AMP and TX) were QLabels, or containers of labels, that only a mouse could use. That's the interactive-QLabel anti-pattern in
docs/a11y.md.A new
StatusIndicatorhelper (src/gui/StatusIndicator.{h,cpp}) is attached to each indicator:Qt::TabFocus, so a mouse click still doesn't take focus. Disabled indicators stay out of the Tab order, as Qt does.MainWindow's app-wide filter binds Space to PTT (Hold), and it runs before any widget's filter. So with a radio connected, Space keys PTT on a focused indicator exactly as it does on every other button. The helper never claims Space.ObjectDestroyed), and the next query builds the Button (ObjectCreated).color.canon.cyanunderline while it has keyboard focus. It's a child bar, not a stylesheet rule, because the indicators restyle themselves with whole-sheetsetStyleSheet()calls.One action path.
MainWindow::activateStatusIndicator()holds the ten actions, moved verbatim from the event filter's press branches, so mouse and keyboard run the same code. TX still answers only the left mouse button. The ASR indicator is compared only underAETHER_ASR_ENABLED, whichcheck-windowsbuilds without.Accessible names were added to band stack, TNF, CWX, ASR and FDX. The other five already had them.
Maintainer rulings:
ThemeManager.hincludeConstitution principle honored
Principle VI. A focused status indicator never turns Space into anything but the PTT behaviour every other button already has. A test pins it behind the real PTT decision.
Test plan
status_indicator_test(33 cases, offscreen):StateChangedper real changeObjectDestroyedbeforeObjectCreatedpttHoldKeyStep()with a radio connected: Space on a focused indicator is PTT and doesn't activate it; Return activates it and keys nothingcheck-windowsbuilds): the MainWindow sources compile. Restoring the old comparison reproduces "'m_asrIndicator' was not declared in this scope".DEMO-0001,AETHER_AUTOMATION_NO_TX=1, isolated settings):transmitting=false, the indicators carry their names, and a click on the band stack opens its panel.ctest: 673/673. The colour ratchet is flat, and the gates match the merge base apart from the two new files.core/ThemeManager.hgains one includer,StatusIndicator.cpp, for the underline's colour token (maintainer-accepted).ObjectDestroyed/ObjectCreated.check_a11y. It can't see this change, because the indicators are declared in the header and handled in another file, so its silence isn't evidence.Checklist
color.canon.cyan)🤖 Generated with Claude Code