Skip to content

fix(gui): reopen the connection dialog when startup auto-connect gives up — Principle XI. - #5368

Open
crypticpy wants to merge 2 commits into
aethersdr:mainfrom
crypticpy:pr/startup-autoconnect-lockout
Open

fix(gui): reopen the connection dialog when startup auto-connect gives up — Principle XI.#5368
crypticpy wants to merge 2 commits into
aethersdr:mainfrom
crypticpy:pr/startup-autoconnect-lockout

Conversation

@crypticpy

Copy link
Copy Markdown

Summary

Fixes #5367.

MainWindow suppresses its "no saved radio" connection-dialog popup whenever
LastConnectedRadioSerial is set, and covers the window with a "Looking for your
radio…" overlay instead. When the saved radio then cannot be reached, the bail
paths in ConnectionPanel::probeRadio() only call setManualMessage() — writing
the reason onto a page behind a dialog that never opens.

The operator is left with a spinner that never resolves and no offered route back
into the connection UI; recovering requires knowing that
Settings → Connect to Radio… exists. Reproduced on an Icom whose network password
was not persisted: every launch re-entered the same dead end.

ConnectionPanel now reports a startup bail upward as
startupConnectUnavailable, and MainWindow clears the overlay, shows the
reason, and opens the dialog.

  • m_startupProbe is latched rather than assigned: the Icom keychain read
    re-enters probeRadio() without restoreSavedFamily, and that second pass is
    still the same startup attempt. It is cleared on a proven connect and by
    onManualConnectClicked().
  • Interactive and automation probes stay silent — their operator is already
    reading the manual page, and popping the dialog would replace the specific
    message with a status line.
  • showConnectionDialog(), not toggleConnectionDialog(): toggling would hide a
    dialog the operator opened while the probe was still in flight.

Covers the three credential/config bails reachable from startup: missing Icom
user name, no saved Icom password, and an unavailable saved source path. A
wrong saved password fails inside the RS-BA1 session rather than in
probeRadio() and is deliberately not addressed here — noted as follow-up so
the scope stays the reported dead end.

Constitution principle honored

Principle XI — Fixes Are Demonstrated. New regression test
startup_autoconnect_lockout_test pins the contract MainWindow relies on. Two
mutations verified: dropping the report call fails 3 checks (the lockout
returns); dropping the startup-only guard fails 2 (interactive probes wrongly pop
the dialog).

Principle IX — Surface Only What Survives. The failure reason was already
being computed and then discarded behind an overlay; this routes it to a surface
the operator can actually see.

Test plan

  • Local build passes (cmake --build build) — clean, exit 0
  • Behavior verified on a real radio if applicable — yes: the original dead
    end was hit on a real Icom over RS-BA1 whose network password had not been
    persisted, which is what prompted the fix
  • Existing tests pass (CI) — startup_autoconnect_lockout_test passes. Full
    local suite shows only the two failures that reproduce unchanged on clean
    main (bridge_docs_check, hl2_state_restore_test), neither related to
    connection startup
  • Reproduction steps documented if user-reported bug — in Startup auto-connect failure leaves a permanent "Looking for your radio…" overlay with no way back to the connection dialog #5367

Checklist

  • Commits are signed (docs/COMMIT-SIGNING.md) — GPG, GitHub reports
    verified: true
  • No new flat-key AppSettings calls — this change adds no settings; it
    only reads the existing LastConnectedRadioSerial condition already in
    MainWindow
  • Code is clean-room — AetherSDR's own connection UI (Principle IV)
  • All meter UI uses MeterSmootherN/A, no meter UI is touched
  • Documentation updated if user-visible behavior changed — no doc change
    needed; no document describes the startup overlay's bail behaviour.
    CHANGELOG.md deliberately untouched
  • Security-sensitive changes reference a GHSA if applicable — N/A. Note
    this touches credential flow control only: no credential value is
    logged, surfaced, or newly persisted — the dialog shows the same
    setManualMessage() reason string that already existed

Note on the claim protocol (AGENTS.md §Issue / PR Claim Protocol): assignee
changes are rejected for an account without write access to this repo, so the
Fixes #5367 link is the visible claim on the issue timeline instead.

…s up — Principle XI.

MainWindow suppresses its "no saved radio" connection-dialog popup whenever
LastConnectedRadioSerial is set, and covers the window with a "Looking for your
radio…" overlay instead. When the saved radio then cannot be reached, the bail
paths in ConnectionPanel::probeRadio() only call setManualMessage() — writing
the reason onto a page behind a dialog that never opens.

The operator is left with a spinner that never resolves and no offered route
back into the connection UI; recovering requires knowing that
Settings -> Connect to Radio... exists. Reproduced on an Icom whose network
password was not persisted: every launch re-entered the same dead end.

ConnectionPanel now reports a startup bail upward as startupConnectUnavailable,
and MainWindow clears the overlay, shows the reason, and opens the dialog.

- m_startupProbe is latched rather than assigned: the Icom keychain read
  re-enters probeRadio() without restoreSavedFamily, and that second pass is
  still the same startup attempt. Cleared on a proven connect and by
  onManualConnectClicked().
- Interactive and automation probes stay silent — their operator is already
  reading the manual page, and popping the dialog would replace the specific
  message with a status line.
- showConnectionDialog(), not toggleConnectionDialog(): toggling would hide a
  dialog the operator opened while the probe was still in flight.

Covers the three credential/config bails reachable from startup: missing Icom
user name, no saved Icom password, and an unavailable saved source path. A
*wrong* saved password fails inside the RS-BA1 session rather than in
probeRadio() and is not addressed here — follow-up.

Regression test startup_autoconnect_lockout_test pins the contract MainWindow
relies on. Two mutations verified: dropping the report call fails 3 checks
(the lockout returns); dropping the startup-only guard fails 2 (interactive
probes wrongly pop the dialog).

Suite: 316/318 pass, 1 skipped. The 2 failures (bridge_docs_check,
hl2_state_restore_test) reproduce unchanged on clean main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015HjpgedubkqsxhbYrsTzMR
@crypticpy
crypticpy requested a review from a team as a code owner September 1, 2026 04:06

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue fit

#5367: a saved radio unreachable at startup left the operator on a permanent "Looking for your radio…" overlay — the connection-dialog popup is suppressed whenever LastConnectedRadioSerial is set, so a bail that only calls setManualMessage() writes onto a page behind a dialog that never opens. The mechanism the PR adds — startupConnectUnavailable handed up to MainWindow, gated on m_startupProbe (latched across the Icom keychain re-entry, cleared on proven connect / manual takeover) — is exactly right, cleanly reasoned, and well-tested (startup_autoconnect_lockout_test pins the startup-reports / interactive-stays-silent contract). The MainWindow handler guards (!m_userDisconnected && !isConnected(), show-not-toggle) are correct.

But the fix is wired into only some of probeRadio's bail paths, and two of the ones it misses are central cases of "saved radio cannot be reached" — the very symptom.

Scope

Three files + test, all on-issue. CHANGELOG.md untouched. Preflight: no sockets, no fake peers.

Blockers

1. Two startup-reachable bails still strand the overlay (both untouched context, so referenced by line rather than anchored inline). reportStartupProbeFailure was added to the source-path-unavailable and Icom-credential bails, but not to:

  • DNS resolution failuresrc/gui/ConnectionPanel.cpp:2752 ("Could not resolve \"%1\"…"): a saved radio addressed by hostname whose name stops resolving (DHCP/DNS change) bails with setManualMessage only.
  • HL2 no-answersrc/gui/ConnectionPanel.cpp:2795 ("No Hermes-Lite 2 answered at %1…", the Hl2ProbeResult::NoAnswer arm): a saved Hermes-Lite 2 powered off or unreachable at startup bails with setManualMessage only.

Both are reached on the restoreSavedFamily startup path (the saved route resolves to a hostname / to HL2), so an operator with a saved HL2 idle at boot, or a saved .local/DNS name that moved, gets the identical stuck overlay #5367 describes. Wrap each in if (!reportStartupProbeFailure(<short reason>)) { setManualMessage(<full reason>, true); } before the return (for HL2, before the shared return covering NoAnswer), and add an HL2-no-answer startup row to the test so completeness is pinned rather than spot-fixed. Any other probeRadio bail reachable under restoreSavedFamily wants the same treatment — worth an audit pass rather than fixing these two by name.

Nits (non-blocking)

  • The three m_startupProbe = false sites (proven-connect at :1401, manual-takeover at :2499, bail-consumed in reportStartupProbeFailure) are each correct and distinct — a one-line "cleared in three places" note near the member declaration would stop a future reader taking one for redundancy.

What was verified vs read

  • Verified by me in the PR head: the three clear-sites are distinct and correct; the Icom-keychain re-entry latch reasoning holds; and — the blocker — the DNS-fail and HL2-no-answer bails call only setManualMessage with no upward report, both reachable under restoreSavedFamily.
  • Read, not re-run: the MainWindow handler guards and the test's startup/interactive contract (CI hasn't run — a merge gate).
  • The automated pass for this PR misfired (it returned #5364's SpectralNR findings) and was discarded; this is a direct manual pass. No bridge session — the stuck-overlay paths are startup-timing GUI state, and the PR's own extracted test is the right coverage vehicle once the two missing bails are wired in.

…e XI.

Review of aethersdr#5368 found two probeRadio() bails that still stranded the
"Looking for your radio…" overlay: an Icom saved by a hostname that no
longer resolves, and a Hermes-Lite 2 that does not answer. Auditing every
exit reachable under restoreSavedFamily turned up more of the same shape:
the three Flex bails (explicit-bind failure, the 3 s no-response timeout,
and a socket error such as connection refused or host not found) and the
HL2 probe's own bails (bind, resolve, send, and already-in-use). None of
them reported upward, so a saved routed Flex that was powered off at boot
had the identical stuck overlay aethersdr#5367 describes.

Every bail now calls reportStartupProbeFailure() with a short reason. The
helper moves to the private section — nothing outside ConnectionPanel
called it — and drops its unused bool return; the manual page keeps its
full message in every case so the operator can still read it once the
dialog is open. A note on m_startupProbe records why its three clear
sites are each distinct.

Test: startup_autoconnect_lockout_test gains an HL2 no-answer row that
lets the real probe run against TEST-NET-1 and waits out its 600 ms
deadline. Mutation-checked: removing the NoAnswer report fails both new
assertions. Socket disclosure is in tests.cmake per AGENTS.md — one
ephemeral UDP port, one datagram, no peer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhDXan9Qe86EbypHmxzHUy
@crypticpy

Copy link
Copy Markdown
Author

@ten9876 Thanks — both bails wired, and the audit pass you suggested found more of the same shape. Pushed as 58ffe69.

Bails now reporting upward (every probeRadio() exit reachable under restoreSavedFamily that does not end in a connect request):

Path Bail Before
Icom hostname does not resolve stranded
HL2 no answer within 600 ms stranded
HL2 (inside probeHermesLite2) explicit bind failed / could not open UDP socket / name has no IPv4 / datagram send failed / radio already in use by another client stranded
Flex explicit bind failed / 3 s no-response timeout / socket error (refused, host not found) stranded

The Flex three were the surprise: a saved routed Flex powered off at boot had the identical stuck overlay #5367 describes, since LastRoutedRadioIp goes through probeFlexRadio() rather than discovery. The one probeRadio() return I left alone is the unparseable custom CI-V address — IcomSettings only persists validated bytes, so a startup probe cannot reach it.

Two small cleanups of my own while in there: reportStartupProbeFailure() moves to the private section (nothing outside ConnectionPanel called it) and drops its unused bool return. I kept the full setManualMessage() text on every bail rather than the if (!report…) form — once the dialog opens the operator lands on the manual page, and the longer message with the "check the radio is powered, idle…" guidance is the useful one there; the short reason goes to the status line.

Test: startup_autoconnect_lockout_test gains the HL2 no-answer row. It lets the real probe run against 192.0.2.10 (TEST-NET-1) and waits out its own 600 ms deadline; dropped, refused by the local stack, or unable to bind are all startup bails, so it holds regardless of the runner's network. Disclosed in the tests.cmake block per the AGENTS.md carve-out: one ephemeral UDP port, one datagram, no peer. Mutation-checked — removing only the NoAnswer report fails both new assertions. I did not add DNS or Flex rows: those need a resolver or a TCP peer in the test, which the test-layer boundary rules out, and the wiring is the same one-liner pattern as the HL2 arm.

Nit: the "cleared in three places, none redundant" note is on the m_startupProbe declaration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Startup auto-connect failure leaves a permanent "Looking for your radio…" overlay with no way back to the connection dialog

2 participants