Repository navigation
Conversation
Preserve the selected-radio hierarchy from aethersdr#6885 while retaining active-path disabling and confirmed single-session switching.
|
Heads up to reviewers, I have approval from @ten9876 for use of a new gradient theme color. Noted in the event that color audit fails. |
ten9876
left a comment
There was a problem hiding this comment.
Review of #6885, reviewed together with the stacked #6889
What I ran: this head (136dba0b1), plus #6889 (d878d7e3c) merged with current main (155850f50). Built on Linux with Qt 6.12.0, the same Qt CI uses.
- Focused tests: 37 CTest registrations.
- A bridge session against the built-in demo radio (
DEMO-0001): offscreen, isolated settings store, TX disabled. No hardware was involved. - Findings that come only from #6889's delta are in my review on #6889.
Issue fit. #6561 is the RFC, approved by @ten9876 on 2026-10-18. #6223 and #6539 are the multi-account SmartLink requests.
- The workspace model and its versioning, one-shot migration, credential isolation and multi-account SmartLink with an auth-generation fence all address those asks. Most of it held up under attack (see the end).
- Two things don't hold yet: the Return/Enter claim, and the operator-profile behavior on connect. Both are below.
CI. Green, but the runs were created at 06:14Z, before main moved (tip 14:09Z), and the PR is now CONFLICTING.
- The only conflict is the generated
docs/architecture/aetherd-touchpoints.md. Regenerating it withtools/gen_touchpoint_manifest.pyresolves it. - Neither
connection_workspace_testnorconnection_panel_size_testis inci.yml's-Rfilter, so green never ran them.
Scope
| File / group | Claimed in body? | Verdict |
|---|---|---|
models/ConnectionWorkspace.{h,cpp} and the ConnectionPanel workspace UI |
Yes | In scope |
SmartLinkClient multi-account and auth fence |
Yes (#6223, #6539) | In scope |
IcomCredentials per-connection scoping, SettingsCredentialPolicy.h, AppSettings.cpp |
Yes | In scope |
AutomationServer / IConnectionAutomation: connect profiles, connect profile … |
Yes | New public bridge surface. Maintainer accepts it. |
docs/user/**, screenshots, screens.json, sidebars.js |
Yes | In scope |
aetherd-touchpoint-tags.json: new models/ConnectionWorkspace.h tagged universal |
No | Nit 4 |
connection_panel_source_pick_test rewrite: new m_manualSourceCandidates seam, SKIP_RETURN_CODE 77 dropped |
No | A good change, but please mention it in the body |
docs/BUILDING.md +5, CMakeLists.txt +1 |
No | Please explain these in the body |
Blockers
1. Return / keypad Enter does nothing in the Move and Copy dialogs on Linux, and this PR's own test fails 3/3 there.
connection_workspace_testfails deterministically on this head, unmerged, with 3 failures in each of 3 runs:[FAIL] Move carries all paths to the destination location [FAIL] confirmed Remove deletes this location's paths only [FAIL] keypad Enter copies the connection to the chosen location- Cause: the focused widget in the Move dialog (
src/gui/ConnectionPanel.cpp:4863) and the Copy dialog (:4391) is a non-editableQComboBox. On Qt 6.12 on Linux, Return and Enter on that combo open its popup; they do not reach the dialog's default button.- I confirmed it with a 20-line standalone Qt 6.12 probe: a
QDialogholding a combo and a defaultOk, thenQTest::keyClick(combo, Key_Return)givesaccepted=0 popupVisible=1. - Instrumenting the test shows
connectionMoveRadiois still visible after the keypress. - The "Remove" failure is a knock-on effect: the Move never happened.
- I confirmed it with a 20-line standalone Qt 6.12 probe: a
- So "Support Return and keypad Enter in profile entry dialogs" doesn't hold in these two dialogs, and "35 focused CTest cases passed" can't have been a Linux run.
- Fix: handle Return/Enter on the combo explicitly, for example by accepting from
QComboBox::activatedor with a small key filter that clicks the default button when the popup is closed. - Separately,
connection_panel_size_testfails identically onmain(horizontal overflow is reachable, not clipped), so that one is pre-existing and not this PR's.
2. Operator profiles override the operator's own audio and layout choices on every connect, and a missing saved device blocks connect. The maintainer ruled that both parts need fixing.
- The selected profile is captured once, at migration (
src/models/ConnectionWorkspace.cpp:157-159). It is re-applied on every connect:applyWorkspaceOperator()atConnectionPanel.cpp:2177/2214/2770, thenMainWindow_Session.cpp:468-488writesPanadapterLayoutand callsapplyAudioDeviceSelection.- Nothing updates the profile when the operator changes devices or layout in Settings. The next connect silently reverts them.
- Please make Settings changes update the active profile, or apply a profile only when the operator explicitly picks one.
- A saved device that is now missing makes
applyWorkspaceOperator()refuse the connect (MainWindow_Session.cpp:873). That includes unattended startup auto-connect, which previously fell back to the default device. An unplugged USB headset now leaves a kiosk sitting on a status line.- The test pins this as intended ("missing saved microphone blocks connect").
- Please fall back to the default device with a visible warning instead.
3. If this lands without #6889, a failed SmartLink reconnect leaves the account locked until restart.
MainWindow.cpp:833locks the account on WAN connect.- The
authFailedreconnect handler (MainWindow_Session.cpp:702-720) closes the reconnect dialog and opens the connection dialog. It never callssetAccountLocked(false)or clearsm_pendingWanRadio. - Sign-in, account switching and account removal are then all refused, and the Disconnect button is hidden while disconnected.
- #6889's
beginExplicitRadioChoiceunlocks it (MainWindow_Session.cpp:333-336atd878d7e3c), so in the stack the user can recover by clicking a tab. On its own this PR has no way out. - Fix: in that handler, add
m_smartLink.setAccountLocked(false); m_pendingWanRadio = {};besidem_wanReconnectTimer.stop();. - This was reasoned from the code. I did not reproduce it: the demo has no SmartLink path.
Nits (non-blocking)
- The legacy flat keys are still live co-owners. This is a maintainer call; I am not citing it as a canon violation.
setConnectedstill writesIcomCredentials::save(),saveManualProfileandIcomSettings::setLastHost(ConnectionPanel.cpp:1513-1525), andlogin()still writesSmartLinkEmail.- So routes live in both
RoutedProfilesJsonand the workspace. Two Locations with the same host and different routes overwrite each other's legacy entry. - The body calls this "retain the legacy settings snapshot", but it isn't a snapshot. Constitution V asks for one owner. Either freeze the legacy keys or explain why they stay live.
ConnectionWorkspace::load()carries fixups for document shapes that never shipped (ConnectionWorkspace.cpp:196-228): the "Existing preferences" and "Existing setup" renames. They run on every load. A user profile literally named "Existing preferences" gets renamed to "Radio operator". Drop them before merge.connect profilesis described as redacted, but nothing is redacted.ConnectionPanel.h:139returnsworkspaceSnapshot()unchanged: emails, hosts, Icom usernames and device IDs.- No secrets leak, because
validaterejects credential-shaped keys. Either redact, or change the doc and body wording.
- The touchpoint tag for
models/ConnectionWorkspace.hisuniversal.- Its content is client UI preference (audio devices, layout, Locations). The precedent is
CwDecodeSettings.h→ui-support. - Also,
SmartLinkClient.cpp, an engine file, now includesmodels/ConnectionWorkspace.honly to get a key string.
- Its content is client UI preference (audio devices, layout, Locations). The precedent is
- Per-connection Icom keychain entries (
icom/<hash>/password) are never deleted. They are shared across Locations by hash, so deleting one needs a reference check. Please at least say so in the Remove confirmation. SmartLink removal does delete its key correctly. - The scoped-key formats are hand-written in three places:
SettingsCredentialPolicy.h,ConnectionWorkspace::credentialKey, andIcomCredentials.cpp(twice). Build them from the policy table so they can't drift. - Migration always creates an "Existing account" SmartLink entry, even with no prior SmartLink use (seen on a clean store:
{"id":"legacy","name":"Existing account","email":""}). Also,RecentConnectByIpAddresseshosts are all taggedflex. - Weak tests. "switching fences pending asynchronous authentication" only checks that
m_authGenerationincremented, so deleting everygeneration != m_authGenerationguard would still pass. Deliver a stale reply instead. "legacy source is retained" checkscontains(), not that the value is unchanged. - Small items.
- Operator-dialog button labels are not wrapped in
tr()(ConnectionPanel.cpp:4944). - The friend struct name
ConnectionPanelSmartLinkTestAccessis misleading. - A 129–254-character email can't sign in, because the name is capped at 128 by
validate.
- Operator-dialog button labels are not wrapped in
- Main moved. Whichever of #6885 / #6889 lands second needs
tools/gen_touchpoint_manifest.pyre-run.
What I tried to break that held
- Newer schema, corrupt or unavailable store:
load()returns{}andsave()refuses, so a newer document is never overwritten. - Migration is one-shot: the document's existence is the marker, and a failed save retries next launch.
- Credentials in the document or exports:
hasSecretsrejects them at save. Scoped keys are reserved at thesetValue, import and export seam. The test provessetValue(scopedKey)lands in the session vault. - The SmartLink fence covers the login and refresh replies and the keychain read.
logout,setAccountIdandloginare refused while locked. - Removing the last or the selected Location or operator is refused, and empty or duplicate names are refused. Removing the Location that holds the connected radio leaves the session alone. Dialogs follow the
CanonWindowexception and new widgets carry accessible names. - Engine-boundary and command-plane gates, run on merge base and head and compared per file: identical except for line shifts. The theme seed check passes.
color.brand.gradientis an existing token, not a new one. - Bridge, on the demo:
- A clean store migrates to Home/POTA/Club/RV plus "Radio operator".
- Connecting the demo saves it to Home:
connect profilesshows{"family":"sim","serial":"DEMO-0001","kind":"local"}under Home. - A saved TEST-NET manual connection (
192.0.2.10) persists. - The workspace survives a process restart.
… Remote Access, taskbar and metainfo claims. Principle VIII. - AetherLab saves a JSON session (no CSV export) and does not inhibit radio TX (#6481). - README and ROADMAP qualify FLEX-6000 Remote Access as not yet run end to end from AetherSDR, as the CHANGELOG entry does (#6945). - The canon workspaces are windows of their own; the taskbar claim is dropped, since Memory Channels is an owned window (#6931). - #6897's entry states that an old non-straddling CW/CWL preset is dropped on load; #6950's agent is 63 KB packaged, about 110 KB on the radio. - The metainfo description says seven NR engines and a TCI server, matching the README. - ROADMAP and README carry approved RFC #6561 as in flight (#6885 open). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…6961) ## Intent Prepare v26.11.1 at cutoff `ab47610d3`: the six release files, a CHANGELOG section mapped from every PR in `v26.10.4..ab47610`, the README/ROADMAP refresh, and the Stable docs snapshot. Tag the resulting `main` commit afterwards with `/tag-release`. ## Summary Six-file release prep for **v26.11.1**, plus the Stable docs snapshot. Metadata and docs only: no source, test or CI changes. Cutoff: `origin/main` at `ab47610d3` (#6848, merged 2026-11-01). Anything that merges after it is folded in before the tag. **44 merged changes** from **11 human contributors**, AetherClaude (0) and Dependabot (0). First-time contributors: **@oz457** and **@vu2cpl**. Both were confirmed by searching for a prior merged PR per login before the v26.10.4 timestamp, not from `author_association`. ## Changelog Built from `v26.10.4..ab47610` mapped through the commit-to-PR API (`repos/…/commits/<sha>/pulls`), not from commit subjects: 44 PRs and 0 direct commits, every one cited in the section. `v26.10.4` is an ancestor of `main`, so nothing is excluded. The notes lead with Install update on Windows, macOS and Linux (#6777, #6927/#6930, #6810/#6891/#6925). Next come Remote Access QR sign-in and FLEX-6000 support (#6932, #6945, #6951), container environments and radio health tools (#6949, #6950), AetherLab (#6481), eight canon windows, and the TCI 2.1 changes. Numbers in the section come from the PR bodies. Bodies that disagree with themselves were kept out of the section: #6810 says 333 files plus 4 links where #6891 says 333 entries (329 + 4), #6858 says "four gaps" but lists five, and #6886 gives a 1–8 frame lookback against "at most 9 frames" lost. None of those numbers is quoted. ## Docs refresh, not just version strings - **README**: the Highlights in-app updates line now says the Updates window installs on all three platforms. The CGNAT/TCI line adds FLEX-6000, QR sign-in and TCI protocol 2.1, replacing "TCI v2.0". A new AetherLab highlight is added. In the Roadmap section, In-app updates leaves the list and the canon line names this cycle's windows. Tree checks: six backend directories, seven NR methods. Both unchanged. - **ROADMAP**: - **In flight:** In-app updates is removed as shipped. The canon item gains the v26.11.1 windows, and Hermes-Lite 2 gains the DSB/CW/CWL/FM receive filter (#6897). - **Queued:** nothing shipped from it. - **Open RFCs:** checked against `label:rfc`. #6799 (FFT FPS sets every pan), #6790 (radio-tab AT chip) and #6816 (amber TGXL STANDBY) join. None leaves. Approved RFC #6561, which was in neither list, joins In flight (above). - **Recently shipped:** this is the month rollover. The intro names v26.11.x, the list is trimmed to the cycle, and it holds five new `(v26.11.1)` entries. ## Judgment calls flagged for the maintainer - **In-app updates (RFC #5304) counts as shipped.** All six phases have merged and the RFC is closed, so the item leaves In flight in both ROADMAP and README and becomes a Recently shipped entry. The CHANGELOG still says the first real in-place Windows update runs from this release to the next. - **The Recently shipped rollover trim.** The 31 v26.10.x entries (8 from v26.10.1, 9 from v26.10.2, 8 from v26.10.3, 6 from v26.10.4) leave ROADMAP.md. They remain in CHANGELOG.md. - **Approved RFC #6561 goes in flight.** The radio connection workspace has been approved since 2026-10-18 and its implementation is open in #6885, so ROADMAP In flight and the README Roadmap list carry it, with nothing shipped yet. - **The checker's zero-bot intro shape is fixed in a separate PR**, not here, so this prep stays at its six files plus the snapshot. - **Proceed with main's Full Suite red at the cutoff.** See the heading below. - **Opened ready for review.** ## main CI at the cutoff - **Full Suite on `ab47610d3`: the first attempt failed because the runner's disk filled, not because a test failed.** The link of `aetherd_pan_decode_test` failed with `ld: final link failed: No space left on device`, and `/__w` was at 100% ([run 41351482051](https://github.com/aethersdr/AetherSDR/actions/runs/41351482051)). Attempt 2 is in progress. The parent `9fb0ece95` finished green. - **The weekly sanitizer lane was red on its last three runs.** It runs TSan failures that #6952 fixes and a TSan build that #6948 repairs. Only the next scheduled run on `main` shows whether those fixes hold, and this prep does not wait for it. ## Release-pipeline firsts at this tag (for `/tag-release`) - This is the first tag with the Linux release tarballs (#6891) and the macOS `.app.tar.zst` tarballs (#6930). The asset set becomes **25** (#6930's count). Signing and the Update Manifest run with tarballs present for the first time. - Not a hotfix: the Store `.msixupload` is expected. ## Scope | file | change | |---|---| | `CMakeLists.txt` | `project(AetherSDR VERSION 26.11.1)` | | `README.md` | Current version line, plus the prose refresh above | | `AGENTS.md` | Current version line | | `CHANGELOG.md` | new `[v26.11.1] — 2026-11-01` section; `[Unreleased]` stays and stays empty; history below byte-identical | | `packaging/linux/io.github.aethersdr.aethersdr.metainfo.xml` | `<release version="26.11.1" date="2026-11-01"/>` first in `<releases>`; the description says seven NR engines and a TCI server, matching the README | | `ROADMAP.md` | cycle heading → post-v26.11.1, plus the prose refresh above | | `docs/user/versioned_docs/version-stable/**`, `docs/user/versioned_sidebars/version-stable-sidebars.json`, `docs/user/stable-version.json` | docs snapshot (`tools/docs/snapshot_stable.py 26.11.1`, 103 pages, its own commit). It adds the new `aetherlab`, `radio-health-tools` and `waveform-environment` pages. | ## Validation Output of `check_release_prep.py --mapping mapping.json`: **30 checks, 2 FAIL, 1 WARN**. All three are explained below. ``` release-prep check: v26.11.1 previous v26.10.4 base origin/main tree /home/jeremy/agent-worktrees/46dbb397/wt-release-v26.11.1 PASS v26.10.4 is an ancestor of origin/main PASS all six version spots say 26.11.1 CMakeLists.txt, README.md, AGENTS.md, ROADMAP.md (cycle heading), CHANGELOG.md (newest section), packaging/linux/io.github.aethersdr.aethersdr.metainfo.xml (first <release>) PASS CHANGELOG heading date equals metainfo release date 2026-11-01 PASS appstreamcli validate ✔ Validation was successful. PASS metainfo <release> list is date-descending with unique versions 26 entries, newest ('26.11.1', '2026-11-01') PASS `## [Unreleased]` stays and stays empty PASS `## [v26.11.1]` sits directly under `[Unreleased]` PASS the previous section is `[v26.10.4]` PASS CHANGELOG diff is exactly the inserted section 73 lines inserted, history below byte-identical to origin/main PASS new section opens with a `### <headline>` Install update on Windows, macOS and Linux · Remote Access on FLEX-6000 radios with QR sign-in · AetherLab, an experimen FAIL intro sentence has the house shape `N merged changes from M human contributors, AetherClaude and K Dependabot updates within that total.` PASS new section has a `### Contributors` block PASS release_contents.py --check (citations, intro and Contributors counts) section: [v26.11.1] PRs in range: 44 cited: 44 PASS every PR in the range is cited INFO citations outside the range (3) — each must be an issue, an RFC or a deliberate prior-PR reference: #5304 RFC closed RFC: AetherSDR In-App Update Design #6226 RFC closed [RFC] Adopt the AetherSDR style guide as the visual canon (About window as the first implementation) #6471 RFC closed [RFC] AetherLab: RigExpert antenna analysis in a station workspace tab PASS intro counts: section says 44/11, mapping says 44/11 PASS Contributors paragraph matches the mapping (11 entries) PASS first-time contributors: ['oz457', 'vu2cpl'] PASS code fences balance in README.md PASS no duplicated table headers or doubled `---` in README.md PASS code fences balance in ROADMAP.md PASS no duplicated table headers or doubled `---` in ROADMAP.md PASS code fences balance in CHANGELOG.md (new section) PASS no duplicated table headers or doubled `---` in CHANGELOG.md (new section) PASS every local link in README.md resolves PASS every local link in ROADMAP.md resolves PASS no heading removed from README/ROADMAP is still linked elsewhere PASS Recently shipped `(vX)` markers run newest-first and lead with the new version 5 entries marked (v26.11.1); versions present: v26.11.1 PASS Recently shipped intro names the current cycle (v26.11.x) FAIL only the four current-version lines lose the `26.10.4` string outside CHANGELOG historical mentions must stay: - **In-app updates** ([RFC #5304](#5304)) — v26.10.4 shipped the RFC #5304 (v26.10.4). unsigned amplifier/tuner meter handles (v26.10.4). (v26.10.4). recentering, and the G2's filter bank and speaker mute (v26.10.4). scrolling for touch screens. RFC #6775 (v26.10.4). Qt TLS plugin that disabled SmartLink, update checks and downloads (v26.10.4). "label": "26.10.4" PASS diff touches no src/, tests/, third_party/ or .github/workflows/ path PASS all six release files are in the diff WARN files beyond the six (each needs a reason in the PR body's scope table) docs/user/stable-version.json docs/user/versioned_docs/version-stable/aetherlab.md docs/user/versioned_docs/version-stable/automation-bridge-and-mcp.md docs/user/versioned_docs/version-stable/callsign-lookup.md docs/user/versioned_docs/version-stable/cw-decoder.md docs/user/versioned_docs/version-stable/dvk-panel.md docs/user/versioned_docs/version-stable/generated/tci-commands.md docs/user/versioned_docs/version-stable/hermes-lite-2.md docs/user/versioned_docs/version-stable/kat500-tuner-control.md docs/user/versioned_docs/version-stable/keyboard-shortcuts.md docs/user/versioned_docs/version-stable/low-bandwidth-connections.md docs/user/versioned_docs/version-stable/memory-channels.md docs/user/versioned_docs/version-stable/menu-reference.md docs/user/versioned_docs/version-stable/midi-controller-mapping.md docs/user/versioned_docs/version-stable/mqtt-station-automation.md docs/user/versioned_docs/version-stable/net-scheduler.md docs/user/versioned_docs/version-stable/panadapter-controls.md docs/user/versioned_docs/version-stable/radio-health-tools.md docs/user/versioned_docs/version-stable/support-and-logging.md docs/user/versioned_docs/version-stable/tailscale-remote-access.md docs/user/versioned_docs/version-stable/tci-server.md docs/user/versioned_docs/version-stable/troubleshooting.md docs/user/versioned_docs/version-stable/ulanzi-dial.md docs/user/versioned_docs/version-stable/updates.md docs/user/versioned_docs/version-stable/waveform-environment.md docs/user/versioned_docs/version-stable/windows.md docs/user/versioned_docs/version-stable/workspace-canvas.md docs/user/versioned_sidebars/version-stable-sidebars.json PASS git diff --check INFO maturity wording per family across README / ROADMAP / new CHANGELOG section — read these against each other Hermes-Lite 2 README early, experimental Hermes-Lite 2 ROADMAP experimental, supported Hermes-Lite 2 CHANGELOG - Icom README early, experimental, supported Icom ROADMAP early, supported, transmit confirmation Icom CHANGELOG - ANAN README experimental, receive-only, supported ANAN ROADMAP experimental, receive-only ANAN CHANGELOG - RTL-SDR README experimental, receive-only, supported RTL-SDR ROADMAP experimental, receive-only RTL-SDR CHANGELOG - 30 checks: 2 FAIL, 1 WARN ``` - **FAIL "intro sentence has the house shape"**: a false positive. The check requires the literal `…human contributors, AetherClaude and…`, but AetherClaude and Dependabot have no commits in this range, so the sentence says `44 merged changes from 11 human contributors.`. v26.10.4's intro still named its one Dependabot update; this range has no bot commits at all, so neither name appears. The counts check (`release_contents.py --check`) passes: 44/11, 11 Contributors entries, first-timers match. - **FAIL "only the four current-version lines lose `26.10.4`"**: the removed lines are the In-app updates In flight entry and the v26.10.x Recently shipped entries, both removed by maintainer decision (above). No version string was replaced. The mentions inside the kept entries are untouched. - **WARN extra files**: the docs snapshot, listed in the scope table. - Docs: `npm ci && npm run build` in `docs/user` succeeded, and `node tools/docs/test_log_rules.mjs` passed 767 checks for 49 rules. - AetherLab's entry follows #6481 and its user guide: each acquisition, cancellation or error *attempts* the analyzer stop and closes USB, physical RF silence is unverified, radio TX is not inhibited, sessions save to JSON (there is no CSV export), and the feature is an experimental proof of concept with hardware sweeps qualified on one Stick Pro 108 on macOS (README, ROADMAP and the CHANGELOG headline, intro and entry). - FLEX-6000 Remote Access is qualified as not yet run end to end from AetherSDR in the README and ROADMAP, as it is in the CHANGELOG (#6945). - No configure, build or test run. This is metadata, and CI builds it. After merge: tag the resulting **main** commit, not this branch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Closes #6561 #6223 and #6539.
Connection workflow
Choose a Location, select a saved or discovered radio, and press Connect. Locations organize radio connections; the window is titled Connect to a radio, and its blue list heading identifies the selected Location, such as Radios at Home.
Persistence and integration
ConnectionWorkspacefeature document throughAppSettings. The internalstationscollection name is retained for saved-document compatibility.CanonWindowand existing Canon theme tokens. Update connection documentation and its screenshot catalog.connect profilesfor redacted workspace inspection andconnect profile stations|operators <id>for selection. Creation and editing use the normal widgets through the existinginvokebridge command.Tests and exercised flows
connection_workspace_testconnection_panel_source_pick_testnetwork_path_resolver_testicom_credentials_singleflight_testconnection_panel_size_test,startup_autoconnect_lockout_test,app_settings_safety_*Recorded results: 35 focused CTest cases passed. The app build, user-guide build and listed static checks completed successfully.
Screenshots
Home:
POTA:
Cabin:
Operator selection:
Operator creation: