Skip to content

feat(gui): Connect to Radio is a CanonWindow, re-skinned to the canon palette. Principle VIII. - #6341

Merged
ten9876 merged 2 commits into
mainfrom
canon-connection-panel
Oct 10, 2026
Merged

ten9876 merged 2 commits into
mainfrom
canon-connection-panel

Conversation

@ten9876

@ten9876 ten9876 commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Builds on #6339 and #6340, both merged.

Summary

Part of #6239. Connect to Radio becomes a CanonWindow, following the About window's recipe as AetherRX, AetherTX (#6334), AetherModem (#6339) and AetherMap (#6340) do:

  • Chrome: no title bar, the ambient ground, a canon header ("Connect to a Radio", replacing the in-body heading) and the corner close button. Drag any empty area to move it, resize it from every edge, and close it with Esc or Ctrl+W.
  • Kind: still a parented dialog (Kind::Dialog). MainWindow still anchors it above the status bar; the new setOwnerPlaced() stops CanonWindow from centering it on first show.
  • Return: the new setReturnClicksDefault(false) means Return in a field (an IP address, a SmartLink password) only does what that field does. Without it, QDialog would also press the first auto-default button.
  • Frameless mode: the window no longer follows View → Frameless Window. setFramelessMode(), the frameless title bar, the opaque paintEvent() and the framed branch of screenFitFrameMargins() are removed. The Connect to Radio window places Disconnect button below the Windows taskbar #4515 screen fitting and the Panadapter and Waterfall Update Rates Significantly Degraded on Linux AppImage v26.8.1 (Wayland) #4725 xcb re-show path are unchanged.

Look: it sits under the canon/connection theme scope, with a canon sheet in Network Diagnostics' vocabulary:

  • groups are raised cards with muted titles, the radio list is nested and fields sit on control;
  • mode cards are raised, and the selected one has a cyan edge;
  • selection and focus are cyan, and check boxes use the canon indicators;
  • hint, info and error labels use canon muted, canon cyan and accent.danger.

CanonWindow:

  • gains setReturnClicksDefault() (the Workspace kind's Return handling, now a setting a dialog can turn off) and setOwnerPlaced();
  • now owns the header helper makeCanonHeader(), moved from StagePage, so every canon window can reach it without the stage-page sources.

Touchpoint manifest: StagePage drops its now-unused ThemeManager include, so the ThemeManager.h includer count goes from 173 to 172. There are no new touchpoints.

Docs:

  • first-connection.md describes the window.
  • docs/style/* list Connect to Radio among the canon windows, and dialog-patterns.md documents the two new options.
  • Stale screenshot, to recapture after merge: connect-dialog.png (it still shows the old title bar and heading).

Constitution principle honored

Principle VIII (Evidence Over Assertion): the checks below, each mutation-verified. The visual direction is RFC #6226's.

Test plan

  • Local build passes
  • canon_window_test: a dialog with Return opted out ignores Return in a field, and an owner-placed window keeps its position on first show. Each check fails when its option is removed.
  • connection_panel_size_test: the framed cases are dropped, since the window has no frame any more. The growth check now settles the layout over two event-loop turns, because the body sits one layout deeper. It still fails with fitToScreen()'s growth branch disabled.
  • startup_autoconnect_lockout_test passes, now linking CanonWindow.cpp instead of FramelessWindowTitleBar.cpp.
  • startup_autoconnect_lockout_test also checks that attaching an already signed-in SmartLink client replaces the sign-in hint with the account. No socket is opened. The check fails when the account line's state is read back from its stylesheet, as it was before the review fix.
  • The static-check scripts pass locally, and the touchpoint manifest is regenerated.
  • Built locally for manual testing: the window opens above the status bar; Esc, Ctrl+W and ✕ close it; it can be dragged; all three pages are canon.

Checklist

  • Commits are signed
  • No new AppSettings keys
  • Code is clean-room
  • All meter UI uses MeterSmoother (unchanged)
  • Documentation updated: docs/user/docs/first-connection.md, docs/style/*
  • Security-sensitive changes: n/a (the SmartLink login fields are only restyled)

🤖 Generated with Claude Code

… palette. Principle VIII.

ConnectionPanel follows About's recipe, as AetherModem and AetherMap do:
- CanonWindow chrome: no title bar, the ambient ground, a canon header
  ("Connect to a Radio", replacing the in-body heading) and the corner close
  button. Drag any empty area to move it, resize it from every edge, and
  close it with Esc or Ctrl+W.
- Still a parented dialog (Kind::Dialog). MainWindow still anchors it above
  the status bar, so setOwnerPlaced() keeps CanonWindow from centring it on
  first show.
- setReturnClicksDefault(false): Return in a field (an IP address, a
  SmartLink password) does only what the field does, rather than also pressing
  the first auto-default button, as a QDialog otherwise would.
- It no longer follows View -> Frameless Window, so setFramelessMode(), the
  frameless title bar, the opaque paintEvent() and the framed branch of
  screenFitFrameMargins() are gone. The #4515 screen fitting and the #4725
  xcb re-show path are unchanged.

It sits under canon/connection with a canon sheet in Network Diagnostics'
vocabulary:
- groups are raised cards with muted titles, the radio list is nested and
  fields sit on control;
- mode cards are raised, with a cyan edge on the selected one;
- selection and focus are cyan, and check boxes are the canon indicators.
The hint, info and error labels use canon muted, canon cyan and
accent.danger.

CanonWindow gains setReturnClicksDefault() (the workspace kind's Return
handling, now a setting a dialog can turn off) and setOwnerPlaced(). Its
header helper moves from StagePage to CanonWindow (makeCanonHeader()), where
every canon window can reach it without the stage-page sources. StagePage
drops its now-unused ThemeManager include, so the touchpoint manifest's
ThemeManager.h count goes 173 -> 172.

Tests:
- canon_window_test checks that a dialog with Return opted out ignores Return
  in a field, and that an owner-placed window keeps its position on first
  show. Each fails with its option removed.
- connection_panel_size_test drops its framed cases, since there is no frame
  any more. Its growth check settles the layout over two event-loop turns,
  because the body now sits one layout deeper; it still fails with
  fitToScreen()'s growth branch disabled.

The user guide's first-connection page describes the window.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ten9876
ten9876 requested a review from a team as a code owner October 10, 2026 09:10
@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 2 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 11
Files with findings: 2
Total findings : 2

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@ten9876 ten9876 self-assigned this Oct 10, 2026

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Issue fit: #6239 explicitly selects Connect to Radio for the canon conversion, including its parented-dialog kind and placement above the status bar. Head b6ad938c4 implements that direction: ConnectionPanel inherits CanonWindow, opts out of first-show centering and implicit default-button Return handling, adopts scoped token styling, and retains the existing fit/clamp and xcb re-show paths. The header helper relocation is behavior-preserving. No issue requirement is visibly omitted, subject to the platform verification limits below.

Scope

File/group Claimed? Verdict
ConnectionPanel.cpp/.h and MainWindow.cpp Yes Approved chrome/style conversion and integration
CanonWindow.cpp/.h Yes Owner placement, Return option, and header helper
StagePage.cpp/.h and four caller include removals Yes Header-helper relocation, unchanged implementation
Two modified tests and tests.cmake Yes Placement/keyboard/sizing coverage and required linkage
Three style guides and first-connection.md Yes Document approved surface and options
aetherd-touchpoints.md Yes Genuine ThemeManager includer reduction, 173 → 172

All 19 files are explained by the stated change. One relevant signed commit, authored 2026-10-10 08:44:28 UTC; no unrelated/outlier commits. No new settings keys, protocol verbs, dependencies, transmit paths, or enforcement baseline growth. The stale connect-dialog screenshot is explicitly disclosed in the body, as requested by the user-doc workflow.

Blockers

None found.

Nits — non-blocking

  1. Track the SmartLink hint state independently of stylesheet text. src/gui/ConnectionPanel.cpp:1854 still compares m_slUserLabel->styleSheet() with kHintLabelStyle. That constant now contains {{color.canon.muted}}, while applyStyle() stores the resolved color. The equality therefore no longer identifies a hint label.

    Reproduced with the actual panel and client, injecting only the client's authenticated flag/callsign before setSmartLinkClient(); no login, discovery, transport, or keychain operation was initiated:

    before=Sign in to see radios at remote stations.
    authenticated=1 after=Sign in to see radios at remote stations.
    

    Restoring only the merge-base literal hint style in a throwaway control changes the same run to:

    authenticated=1 after=Signed in as TEST
    

    Use an explicit hint/info/error state or a label property rather than stylesheet identity, and add a socket-free already-authenticated attachment regression case. This is non-blocking: ordinary authenticated and serverConnected callbacks explicitly set the account label, so the evidence does not establish a general login failure.

Evidence and attacks

  • Exact head builds locally on Linux/Qt 6.12, using the lean shared-core review configuration. All three focused tests pass: canon_window_test, connection_panel_size_test, startup_autoconnect_lockout_test. Preflight found no added/modified socket-owning test; the latter injects completed probe outcomes and credentials refusals.
  • Independently mutation-checked the production implementation in throwaway copies. Disabling Return suppression fails the new clicks == 0 assertion at tests/canon_window_test.cpp:200; ignoring owner placement fails w.pos() == ownerPos at line 197; disabling fit growth fails the auto-sized panel grows from its prior auto-fit height assertion. The changes did not weaken these claims into passing implementation mirrors.
  • Drove only DEMO-0001 through explicit socket aethersdr-pr6341-review, offscreen, fresh isolated settings, TX disabled. Captured the local, SmartLink, and manual pages and dark/light theme changes. Corner close and menu reopen work; the dialog remains a parented dialog. The bridge reported its placement as {"h":804,"w":760,"x":351,"y":0} on the review screen. The owned review app exited afterward; no operator instance was driven.
  • Source-level lifetime/keyboard audit preserved field-specific returnPressed handlers, hide/reopen ownership, xcb native-window recreation/deferred activation, and screen clamping. Native xcb/Wayland dragging and every-edge resize, Windows/macOS runtime behavior, and real SmartLink authentication were not independently verified; offscreen gesture acknowledgments are not proof of native window-manager behavior. No live radio or transmitter was used.
  • Base/head gates retain the same 102 engine-boundary findings, 70 frozen capability booleans, and 32 files/368 command-plane entries. Both touchpoint manifests check clean. The bot's two accessibility warnings reproduce on the base; neither is introduced here. No model-invocable code-review skill is available, so this was a manual audit with independent canon/scope and lifecycle passes.

Recommendation

Approve with nits, after required CI succeeds. The main placement, keyboard, and sizing claims survived independent behavioral tests and mutation checks; the verified account-label regression is localized and non-blocking. Fix the label-state comparison and recapture the disclosed screenshot. At review time Docs, Static Checks, sanitizer configuration, and Code Quality pass, while build, check-windows, and check-macos are still running. Main is 0156894fb (08:31:58 UTC), and the earliest head run was created at 09:10:36 UTC: no current stale-main timing gap. The review covers this exact head; any subsequent push needs renewed verification.

@ten9876 ten9876 removed their assignment Oct 10, 2026
…n its stylesheet. Principle VIII.

updateSmartLinkUi() told the sign-in hint from an account line by comparing
the label's stylesheet with kHintLabelStyle. Since the canon re-skin that
style carries {{color.canon.muted}}, and the label holds the resolved color,
so the comparison never matched: attaching an already-signed-in SmartLink
client left "Sign in to see radios at remote stations." up instead of the
account.

setAccountLine() now sets the text and style together and records the tone
(hint, info, error); updateSmartLinkUi() reads the record.

startup_autoconnect_lockout_test attaches a client with only its signed-in
flag and callsign set (no login, no socket) and checks the account replaces
the hint. It fails with the stylesheet comparison restored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ten9876

ten9876 commented Oct 10, 2026

Copy link
Copy Markdown
Collaborator Author

Review nit fixed in c85e601: the SmartLink account line's hint state.

  • Fix: ConnectionPanel::setAccountLine() sets the text and style together and records the tone (AccountTone::Hint/Info/Error) in m_slUserTone. updateSmartLinkUi() (src/gui/ConnectionPanel.cpp:1863) reads that record and no longer compares stylesheets. All seven places that set the account line go through the helper.
  • Regression test: startup_autoconnect_lockout_test → checkSignedInClientReplacesHint. It attaches a SmartLinkClient with only the signed-in flag and the callsign set, through a test-only friend (SmartLinkClientTestAccess), so nothing logs in or opens a socket. It then checks that the account replaces the hint:
    [ OK ] signed-in SmartLink client replaces the sign-in hint       Signed in as TEST
    
    With the old stylesheet comparison restored, the same check fails:
    [FAIL] signed-in SmartLink client replaces the sign-in hint       Sign in to see radios at remote stations.
    
  • canon_window_test and connection_panel_size_test still pass. All four static-check scripts pass, and the touchpoint manifest is unchanged.
  • The stale connect-dialog.png is recaptured after merge, as the PR description says.

The two accessibility-bot warnings reproduce on the merge base, as the review found, so this PR doesn't change them.

@github-actions

Copy link
Copy Markdown

Accessibility check findings

This PR touches src/gui/ files with 2 accessibility issue(s).
Full details appear as inline annotations on the diff above.

Files scanned : 11
Files with findings: 2
Total findings : 2

See docs/a11y.md for the canonical
Qt patterns (setAccessibleName, QAccessibleValueChangeEvent,
QAccessibleInterface subclasses, etc.). Structural remediation
(keyboard navigation, custom QAccessibleInterface subclasses)
is tracked in #3288.

Findings are warnings only — the build is not blocked.

@ten9876 ten9876 self-assigned this Oct 10, 2026

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Issue fit

Yes. The authorising item is the tracking issue #6239, which lists under "Picked: tool windows on CanonWindow": "Connect to Radio (ConnectionPanel), a dialog anchored above the status bar, re-skinned to color.canon.* (#6341)". That names all three things the diff does — CanonWindow base, dialog kind (not workspace), color.canon.* re-skin — and it is maintainer-picked scope, so this is not a smuggled restyle. The anchoring requirement is met by the new setOwnerPlaced(true) seam rather than by moving the placement into the window, which keeps MainWindow::showConnectionDialog() (src/gui/MainWindow.cpp:4960-4986) as the single source of the status-bar anchor.

One governance note in the PR's favour rather than against: #6239 says "nothing here authorises restyling a screen as a side effect of other work", and this PR restyles only the screen its own item names.

Scope

File / group What it changes Claimed? Verdict
src/gui/ConnectionPanel.{h,cpp} QWidget → CanonWindow, hex → color.canon.* tokens, setFramelessMode() and paintEvent() deleted, canon header replaces FramelessWindowTitleBar Yes In scope
src/gui/MainWindow.cpp drops the two setWindowTitle/setFramelessMode calls at construction and the setFramelessWindow() propagation Yes — follows from "always frameless" In scope
src/gui/CanonWindow.{h,cpp} m_kind → m_returnClicksDefault, new setOwnerPlaced(), makeCanonHeader() moved in Yes — the two seams this window needs In scope
src/gui/StagePage.{h,cpp} makeCanonHeader() moved out Implied by the move In scope
AetherRxSettingsDialog.cpp, AetherTxSettingsDialog.cpp, Ax25HfPacketDecodeDialog.cpp, PskReporterMapDialog.cpp #include "StagePage.h" removed Mechanical consequence In scope — I checked all four: makeCanonHeader is their only StagePage symbol, and each gets the declaration via CanonWindow.h from its own header (AetherRxSettingsDialog.h:3 etc.)
docs/architecture/aetherd-touchpoints.md ThemeManager count 173 → 172 No, but mechanical In scope — verified: grep -rl '#include "core/ThemeManager.h"' src/ is 173 on main and 172 at this head, the one file being StagePage.cpp
docs/style/*.md, docs/user/docs/first-connection.md canon-window lists, setReturnClicksDefault/setOwnerPlaced documented, user-facing chrome paragraph Yes In scope, and matches the wording precedent in psk-reporter-map.md:13 / aethermodem-packet-radio.md:27
tests/canon_window_test.cpp, tests/connection_panel_size_test.cpp, tests/tests.cmake new coverage for the two seams; frameless-mode arms removed Yes In scope

No new public surface: no protocol verb, settings key, CLI flag or capability field. No CHANGELOG.md entry (correct). No deleted guard — I read the - lines: the screenFitFrameMargins() title-bar fallback and the setFramelessMode() re-fit both existed only for the decorated-window case, which no longer exists, and the #4515 protection they implemented survives in fitAndClampToScreen() + constrainedFrameTopLeft(), both still called from every show path. Nothing in the diff is unexplained by the issue item.

Socket tests: none added, modified or removed. The changed tests use no QTcpServer/QTcpSocket/QUdpSocket/QLocalServer, no peer process and no Fake* radio class; connection_panel_size_test's 127.0.0.1 occurrences are strings fed to the IP field, not connections.

security-audit (CodeGuard): ran as required over the five flagged files; no findings. All 73 hits refute: the CMDI-BACKTICK hits are C++ raw-string delimiters (ConnectionPanel.cpp:78) and markdown code spans in comments; SSRF-PRIV-*/PII-IP are setPlaceholderText examples (ConnectionPanel.cpp:795, :2655-2658) and comment text (:174-175); CMDI-AMP is &nbsp; and Qt mnemonics; PATH-PROC is QProcess::startDetached("open", args) on the app's own bundle path in argument-list form, byte-identical on main; CS-PII-DOB-ISO is the comment Observed 2026-07-31 on the HL2; SEC-PASSWORD is a setEchoMode(QLineEdit::Password) widget with no literal, and the file contains no logging call mentioning pass/Password. The XSS-OBJECT rich-text sites were each checked for escaping — every interpolant is .toHtmlEscaped() or a static colour. Nothing security-relevant is introduced by this change.

Blockers

None.

Nits (non-blocking)

1. src/gui/ConnectionPanel.cpp:1854 — the token conversion turns this guard into dead code.

        if (m_slUserLabel->text().trimmed().isEmpty()
            || m_slUserLabel->styleSheet() == QString::fromLatin1(kHintLabelStyle)) {

This reads state back out of the stylesheet string, which worked only because the old kHintLabelStyle was literal hex set with setStyleSheet(), so styleSheet() returned it verbatim. After this PR, kHintLabelStyle carries {{color.canon.muted}} and goes through applyStyle() → ThemeManager::applyStyleSheet(), which stores the resolved sheet (ThemeManager.cpp:1697-1703: ctx.appliedStylesheet = resolved; … widget->setStyleSheet(resolved)). The comparison can therefore never be true again, so the || arm is permanently false — the diff updated the setter on the next line and left the reader.

I traced the reachability before calling it a blocker and could not find a live path: setSmartLinkClient() is called once, from MainWindow_Session.cpp:671, with a fresh m_smartLink, and there is no stored-session auto-login, so every transition into authed goes through the authenticated or serverConnected handler, both of which set the label text and style explicitly. So this is a guard silently neutered rather than a user-visible regression — but it was put there precisely because the text check alone is insufficient, and it will not come back on its own. Line 1869's styleSheet().isEmpty() check is unaffected (a resolved sheet is non-empty).

Minimal drop-in that restores the intent without re-introducing string-sniffing:

        if (m_slUserLabel->text().trimmed().isEmpty() || m_slUserLabelShowsHint) {

with a bool m_slUserLabelShowsHint{true}; member set alongside each applyStyle(m_slUserLabel, …) call (true at the two kHintLabelStyle sites, false at the kInfoLabelStyle/kErrorLabelStyle ones). If you'd rather keep the shape, comparing against ThemeManager::instance().resolveFor(m_slUserLabel, QString::fromLatin1(kHintLabelStyle)) also works, but it couples the check to resolution order.

2. src/gui/ConnectionPanel.cpp:849 and :1120 — two inputs fall through the body sheet's QWidget rule.

kConnectionStyle opens with

QWidget {
    color: {{color.canon.inkSoft}};
    background: transparent;
}

A QSS type selector matches subclasses, so this matches every input in the window. Every sibling input is rescued by a per-widget sheet — editStyle (:620, :630), lineEditStyle (:814, :825, :900), applyComboStyle on six of the seven combos — but two are not:

  • m_manualIcomBasePortSpin (:849), the only QSpinBox, gets no sheet anywhere in the file.
  • m_manualSourceCombo (:1120) is the one combo with no applyComboStyle() call.

Both therefore render transparent over the canon ground with no field border or focus ring. This is why the sibling canon sheets carry generic input rules — PskReporterMapDialog.cpp:263-277 has QLineEdit, QSpinBox, QDoubleSpinBox { … } plus :focus, and NetworkDiagnosticsDialog.cpp:186-219 and WaveformsDialog.cpp:447-471 do the same for QComboBox/QLineEdit. Both widgets live behind rows hidden by default (Icom custom ports, Manual → Advanced), which is probably why it wasn't spotted. Appending to kConnectionStyle, modelled on AetherMap:

QLineEdit,
QSpinBox,
QComboBox {
    color: {{color.canon.ink}};
    background: {{color.canon.control}};
    border: 1px solid {{color.canon.lineHi}};
    border-radius: 4px;
    padding: 4px 6px;
}
QLineEdit:focus,
QSpinBox:focus,
QComboBox:focus {
    border-color: {{color.canon.aqua}};
}

3. tests/tests.cmake:6805-6810 — connection_panel_size_test keeps two now-unreferenced sources. The PR correctly prunes src/gui/FramelessWindowTitleBar.cpp from startup_autoconnect_lockout_test, but connection_panel_size_test still lists both PersistentDialog.cpp and FramelessWindowTitleBar.cpp. Neither ConnectionPanel.cpp nor the test references PersistentDialog (the only hits are two comments), and FramelessWindowTitleBar is now reachable only through PersistentDialog.cpp:27. Both lines can go, matching the sibling target:

add_executable(connection_panel_size_test
    tests/connection_panel_size_test.cpp
    src/gui/ConnectionPanel.cpp
    src/gui/CanonWindow.cpp
    src/gui/FramelessResizer.cpp
)

4. tests/connection_panel_size_test.cpp:665-667, 698 — the doubled processEvents() is a layout-settle hack. The comment is honest about why ("the body sits inside CanonWindow's own body widget, one layout deeper"), and if a third nesting level ever appears the report() immediately after fails loudly rather than silently, so this is fine as-is. Worth a thought only because the count is now coupled to the widget nesting depth rather than to a settled condition.

5. docs/user/docs/menu-reference.md:240 and linux.md:72 still offer "turn off View → Frameless Window" as the compositor-trouble escape hatch. One more window is now outside that hatch's reach. Not this PR's problem — it is the canon's deliberate design and three merged PRs set the precedent — but the escape hatch's coverage is shrinking, and it may be worth a line in the #6239 tracking issue before the remaining candidates move.

What I tried to break

Findings below are reasoned from the head checkout at /tmp/aetherclaude/pr-6341; I am headless, so nothing here was reproduced at runtime, and anything phrased as "will look like X" is an inference from the QSS/Qt semantics I cite, not an observation.

  • "Re-skinned to the canon palette" — do the tokens resolve? Checked every token the new sheets name against resources/themes/default-{dark,light}.json: all ten color.canon.* (ink, inkSoft, muted, cyan, aqua, raised, nested, control, line, lineHi) plus color.accent.danger exist under scopes.root.tokens.color. No unresolved token, in either theme.
  • Does canon/connection actually inherit the canon scope? This was my strongest suspicion: ThemeManager::scopeForPath() (ThemeManager.cpp:243) is an exact-path lookup that returns nullptr for an undeclared path, and lookupRaw() then falls back to root, not to the nearest existing ancestor — and the theme JSON declares canon but no canon/connection. That would have silently dropped the canon scope's remapping of color.text.primary / color.accent.bright / color.background.* for the file's remaining non-canon styles (lineEditStyle, comboExtraRules). It holds: theme::setContainer() (:2156) calls registerDeclaredContainer(), which calls scopeOrCreate() (:1913), building canon → canon/connection with the parent chain, and loadThemeFile() replays m_declaredContainers after wiping the tree (:519), so a theme switch does not lose it. The ctor also sets the container before applyCanonSheet(), and the labels built with no parent (makeWrappedLabel) are re-resolved on QEvent::ParentChange by the event filter applyStyleSheet() installs. No finding.
  • The deleted screenFitFrameMargins() fallback (#4515). The removed branch synthesised a title-bar-height margin before the native window existed, and the test asserting it was deleted too — exactly the shape of a regression. It survives: CanonWindow's constructor sets Qt::FramelessWindowHint unconditionally (CanonWindow.cpp:49), so QMargins() is the correct pre-show answer, and the test that remains (connection_panel_size_test.cpp:573-578) asserts it. The #4515 clamp itself is untouched.
  • Geometry persistence. CanonWindow::resizeEvent/moveEvent call saveGeometryToSettings() on every move, and MainWindow re-anchors the panel on every open — a saved position fighting the anchor would be a visible bug. It cannot: saveGeometryToSettings() returns immediately on an empty m_geometryKey (:155-157), and ConnectionPanel never calls setGeometryKey(). setOwnerPlaced(true) likewise returns after m_placed = true, so the first-show centring is skipped without disturbing the restore/launch-size logic above it.
  • Escape and Return. ConnectionPanel is now a QDialog, so Escape closes it where the old QWidget ignored it — a real behaviour change, disclosed in first-connection.md:33 and consistent with the three merged sibling windows. setReturnClicksDefault(false) is the mirror image: without it, Return in the IP field would have pressed the first auto-default button on the page (a mode card). Both documented, neither a finding.
  • The automation bridge and screens.json. MainWindow_Session.cpp:2930 finds the panel by findChild<ConnectionPanel*>("connectionPanel"), and docs/user/screens.json drives it by that name through twelve steps. The object name and the IConnectionAutomation surface are unchanged, and the panel is still a QObject child of MainWindow, so findChild still reaches it.
  • Grepped for anything left behind by the removals. No reference to ConnectionPanel::setFramelessMode survives anywhere in src/ or tests/; m_titleBar and FramelessWindowTitleBar are gone from both ConnectionPanel files; the QStyle include removal is safe (no QStyle or style()-> use remains in the file); m_rootLayout is still read by refitToContent() (:3112-3119), so it is not a dead member.
  • Attacked the new tests rather than just reading them. The canon_window_test block falsifies in both directions: if setOwnerPlaced() were a no-op the window would be centred and pos() == ownerPos fails; if the Return opt-out were ignored on Kind::Dialog the clicks == 0 assertion fails. It also asserts windowType() == Qt::Dialog, which pins the Dialog/Workspace distinction the refactor could have blurred. The connection_panel_size_test loop legitimately lost its frameless=false arm because the mode no longer exists.
  • Link/compile completeness for the two test targets. CanonWindow.cpp's non-Qt dependencies are FramelessMoveHelper.h (header-only), SparkWidget (declared in CanonWindow.h, defined in the same TU), AppSettings and ThemeManager (both in aethercore, which both targets link), and applyCanonSheet/canonIndicatorRules are inline in CanonIndicators.h. Nothing is missing — consistent with the green build, check-macos and check-windows.
  • What I could not check. Actual rendering, so nit 2's visual consequence is inferred from QSS subclass matching rather than seen; and CI green here means the -R-filtered subset passed — canon_window_test and connection_panel_size_test are the two that matter and I could not confirm from the check names whether either is in the filter, so treat their result as unverified rather than proven.

Recommendation

Approve with nits. The migration is careful work: the two new CanonWindow seams are narrow and documented, the #4515 screen-fit protection is preserved through the change of chrome rather than re-implemented, the makeCanonHeader move is justified by the fourth caller, the docs match the sibling precedent, and the new test block genuinely falsifies both seams. Nits 1 and 2 are the two places the hex→token conversion changed something the author didn't intend — a guard that now never fires, and two inputs the body sheet strips — and both have drop-in fixes above; nits 3-5 are tidying. None of them breaks a user or violates canon, so this is a maintainer's call on whether to fold the two fixes in before merge or file nit 1 as a follow-up. Thanks for the consistency with #6334/#6339/#6340 — it made the sibling comparisons quick, which is exactly what made nit 2 findable.


🤖 aethersdr-agent · cost: $12.1832 · model: claude-opus-5

@Ozy311 Ozy311 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed c85e6012 by reading the diff. Nothing was built or run, and build, check-macos and check-windows were still running when I looked. Static checks and the docs build are green.

Fit: this follows the same recipe as #6334, #6339 and #6340, and every file in the diff is explained by the body. The two new CanonWindow options are small, and each one replaces a behaviour that used to be hard-wired:

  • setReturnClicksDefault() replaces the old m_kind == Kind::Workspace test. The default is derived from the kind, so both Workspace and Dialog windows behave exactly as before unless they opt out. The only caller that opts out is ConnectionPanel.
  • setOwnerPlaced() only skips the first-show centring. m_placed is still set before the early return, so the geometry-save paths at CanonWindow.cpp:106-141 behave the same.

What I checked

  • The account-line refactor is equivalent to the old code. The new m_slUserTone stands in for the old styleSheet() == kHintLabelStyle comparison. That comparison could no longer work once the label goes through ThemeManager::applyStyleSheet(), because the stored sheet is the theme-resolved one. All seven writers now go through setAccountLine(). The signed-out branch of updateSmartLinkUi() also behaves the same: the old code set the hint text only when the label was empty, and set the hint style only when the sheet was empty. Those two conditions coincided in practice, and the new single call covers both.
  • No callers are left behind. No code in src/ or tests/ still calls ConnectionPanel::setFramelessMode, its title bar or its paintEvent. The setFramelessWindow() fan-out drops only the connect panel, and the panel no longer reads the FramelessWindow key itself.
  • Return in a field. QLineEdit emits returnPressed and then ignores the key, so it reaches CanonWindow::keyPressEvent, which now drops it. The IP field and the SmartLink password field therefore still do their own action, and nothing else.
  • QDialog base. Because ConnectionPanel is now a QDialog rather than a QWidget with a dialog flag, Esc now hides it (disclosed). A first show on a path MainWindow did not position gets QDialog's own centring instead of the window-manager default. If anything that is better than before for the automation bridge's show path.

Notes (non-blocking)

  1. Users who turned off View → Frameless Window lose the native frame on this window. Before this PR the Connect window followed that setting; now it is always frameless. That is the canon direction (RFC #6226), and the body says so. But the setting's main users are tiling-window-manager and screen-reader setups, where a native frame is the handle they rely on. A sentence in first-connection.md saying that the window now ignores the setting would save someone a bug report.
  2. The two accessibility-bot warnings reproduce on the merge base, as your comment says, so they are not this PR's.
  3. The stale connect-dialog.png is already disclosed.

Nothing blocking from me. I'd wait for the three build jobs to go green.

73, K6OZY


Generated by Claude Code

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Cisco CodeGuard — static analysis of this PR (78 finding(s))

  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/Ax25HfPacketDecodeDialog.cpp /tmp/aetherclaude/pr-6341/src/gui/Ax25HfPacketDecodeDialog.cpp:167
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/Ax25HfPacketDecodeDialog.cpp /tmp/aetherclaude/pr-6341/src/gui/Ax25HfPacketDecodeDialog.cpp:1626
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/Ax25HfPacketDecodeDialog.cpp /tmp/aetherclaude/pr-6341/src/gui/Ax25HfPacketDecodeDialog.cpp:1883
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/Ax25HfPacketDecodeDialog.cpp /tmp/aetherclaude/pr-6341/src/gui/Ax25HfPacketDecodeDialog.cpp:1964
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/Ax25HfPacketDecodeDialog.cpp /tmp/aetherclaude/pr-6341/src/gui/Ax25HfPacketDecodeDialog.cpp:2648
  • [MEDIUM] CS-PII-DOB-ISO — PII detected: dob in src/gui/Ax25HfPacketDecodeDialog.cpp /tmp/aetherclaude/pr-6341/src/gui/Ax25HfPacketDecodeDialog.cpp:2651
  • [HIGH] CS-VLN-SSRF-PRIV-10 — Vulnerability: CS-VLN-SSRF-PRIV-10 in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:795
  • [HIGH] CS-VLN-SSRF-PRIV-10 — Vulnerability: CS-VLN-SSRF-PRIV-10 in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2658
  • [HIGH] CS-VLN-SSRF-PRIV-172 — Vulnerability: CS-VLN-SSRF-PRIV-172 in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2657
  • [HIGH] CS-VLN-SSRF-PRIV-192 — Vulnerability: CS-VLN-SSRF-PRIV-192 in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:174
  • [HIGH] CS-VLN-SSRF-PRIV-192 — Vulnerability: CS-VLN-SSRF-PRIV-192 in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:175
  • [HIGH] CS-VLN-SSRF-PRIV-192 — Vulnerability: CS-VLN-SSRF-PRIV-192 in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2655
  • [HIGH] CS-VLN-SSRF-PRIV-192 — Vulnerability: CS-VLN-SSRF-PRIV-192 in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2656
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:1316
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:1425
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2445
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2473
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2858
  • [MEDIUM] CS-VLN-XSS-OBJECT — Vulnerability: CS-VLN-XSS-OBJECT in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:3568
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:174
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:175
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:795
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2127
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2655
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2656
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2657
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/ConnectionPanel.cpp /tmp/aetherclaude/pr-6341/src/gui/ConnectionPanel.cpp:2658
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:9669
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:9781
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:9903
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:9904
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:9905
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:6200
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:6279
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:525
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:555
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:558
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:580
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:3643
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6341/src/gui/MainWindow.cpp:1279

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: $12.3432 · model: claude-opus-5

@ten9876
ten9876 merged commit a9a83e6 into main Oct 10, 2026
10 checks passed
@ten9876
ten9876 deleted the canon-connection-panel branch October 10, 2026 09:34
ten9876 added a commit that referenced this pull request Oct 11, 2026
…remote operation over CGNAT (#6389)

## Summary

Release prep for **v26.10.2**: the six release files plus the Stable
docs snapshot. Metadata and docs only; no source, test or CI changes.

Cutoff: `origin/main` at `46943eb3c` (#6083, merged 2026-10-11).
Anything that merges after it is folded in before the tag.

**106 merged changes** from **15 human contributors**, AetherClaude (2)
and Dependabot (0). First-time contributor: **@dacacioa**. This was
confirmed by checking each login for a merged PR before the previous
tag, not from `author_association`.

## Changelog

The range `v26.10.1..46943eb` was mapped through the commit-to-PR API
(`repos/…/commits/<sha>/pulls`), not from commit subjects. It holds 106
PRs and no direct commits, and every one is cited in the section.
`v26.10.1` is an ancestor of `main`, so nothing is excluded. #6083
merged before the branch was cut, so it is inside the range and needs no
unmerged-PR banner.

The notes lead with:
- the unified 52 px title bar (#6198);
- docs.aethersdr.com (#6286, #6298, #6294);
- remote operation over CGNAT through the in-radio tailnet shim (#6273,
#6318, #6325, #6319);
- the canon windows (#6227, #6334, #6339, #6340, #6341, #6364, #6372,
#6386).

Then come DVK import, FFT Smooth/De-comb, TCI tune and receiver events,
RTL-SDR multi-receiver delivery, HL2 and ANAN-G2 state memory, the
IC-7300MK2 fixes, the first clean sanitizer runs (stated beside #6350's
TSan red) and Authenticode signing.

Numbers come from the PR bodies. "On upgrading" callouts cover the
behavior changes the bodies flagged:
- HL2 receive audio is 12.04 dB quieter (#6254);
- HL2 host CW is +6.02 dB at a fixed drive (#5975);
- ANAN AGC-T is remapped (#6282);
- sleep prevention is on by default (#6195);
- the Tailscale MTU cap has no opt-out (#6319);
- IC-7300MK2 Auto SQL is withdrawn (#6184).

DeepFist (#5953, #6103) is described as applying only to
`ENABLE_DEEPFIST_EXPERIMENT` builds.

## Docs refresh, not just version strings

- **README**:
- Highlights: FFT Smooth/De-comb on the spectrum line; AetherMap naming;
remote over CGNAT and TCI tune/receiver events on the SmartLink/TCI
line; pre-NR Copy Assist; a new docs.aethersdr.com line.
- Supported Hardware: the KPA1500, marked as not yet validated on
hardware.
- Other radio families: HL2 state restore extended; ANAN-G2 gains AGC-T,
NB2, supply voltage/link and per-radio memory; RTL-SDR now "bundled in
every official package", with WFM stereo, PPM, per-receiver SQL/meters
and DAX/TCI feeds, and multi-receiver explicitly an evaluation.
- Roadmap section: the CTR2 line advanced and a visual-canon line added.
  - Maturity labels are unchanged.
- **ROADMAP**:
- In flight: a v26.10.2 sentence each for aetherd, Icom, ANAN-G2,
RTL-SDR (remaining is now production multi-receiver qualification and
digital FM, RFC #6079), Hermes-Lite 2 and the CTR2 relay (RFC #6332
extensions). A new entry for the visual canon (RFC #6226 / #6239).
  - Queued: RFC #4214 (approved 2026-10-09) added.
- Open RFCs, checked against `label:rfc`: #4214 leaves (approved); #4764
and #4861 leave (shipped by #6198 and #6216); #6346, #6232, #6212 and
#6209 join. #6332 is approved and folds into the CTR2 entry.
- Recently shipped: nine new `(v26.10.2)` entries, still within the
v26.10.x cycle, so no month-rollover trim.
- Backends (six: anan, flex, hl2, icom, rtl, sim) and NR engines (seven)
were checked against the tree; both are unchanged.

## Judgment calls flagged for the maintainer

All of these were decided through AskUserQuestion:
- Version **26.10.2**, date **2026-10-11**, opened ready for review.
- **#6350**, the TSan red in the weekly sanitizer lane: proceed, and
name it below.
- RFCs **#4764** and **#4861** come off Open RFCs as shipped. The issues
themselves are left open for you to close.
- RFC **#4214** goes under Queued (next cycle).
- A **visual canon** In-flight entry, plus a README Roadmap line.
- The **KPA1500** is listed in README Supported Hardware with "not yet
validated on hardware".
- Including **#6083**: you asked for it, and it merged before the
cutoff, so it ships as an ordinary part of the range.

## Known failing on `main`

- **`ctr2_hid_thread_port_test`** fails in the weekly TSan lane (runs of
2026-10-08 and 2026-10-10; #6350). `main`'s CI and Full Suite were green
at `414082197`. The CHANGELOG headline and intro say so: the lanes
achieved their first clean runs this cycle, and the test remains failing
under TSan. This prep does not fix the test, and the release ships with
it red in that lane.

## Scope

| file | change |
|---|---|
| `CMakeLists.txt` | `project(AetherSDR VERSION 26.10.2)` |
| `README.md` | Current version line, plus the prose refresh above |
| `AGENTS.md` | Current version line |
| `CHANGELOG.md` | new `[v26.10.2] — 2026-10-11` section; `[Unreleased]`
stays and stays empty; history below byte-identical |
| `packaging/linux/io.github.aethersdr.aethersdr.metainfo.xml` |
`<release version="26.10.2" date="2026-10-11"/>` first in `<releases>` |
| `ROADMAP.md` | cycle heading → post-v26.10.2, 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/user/src/components/LogAnalyzer/rules.json` | docs snapshot
(`tools/docs/snapshot_stable.py 26.10.2`), in its own commit. This is
the first snapshot since #6286. |

## Validation

Output of `check_release_prep.py --mapping mapping.json` at the branch
head: 30 checks, 1 FAIL (explained below), 1 WARN (the snapshot files,
listed in the scope table).

```
PASS v26.10.1 is an ancestor of origin/main
PASS all six version spots say 26.10.2
PASS CHANGELOG heading date equals metainfo release date
PASS appstreamcli validate
PASS metainfo <release> list is date-descending with unique versions
PASS `## [Unreleased]` stays and stays empty
PASS `## [v26.10.2]` sits directly under `[Unreleased]`
PASS the previous section is `[v26.10.1]`
PASS CHANGELOG diff is exactly the inserted section
PASS new section opens with a `### <headline>`
PASS intro sentence has the house shape
PASS new section has a `### Contributors` block
PASS release_contents.py --check (citations, intro and Contributors counts)
     PRs in range: 106  cited: 106; intro 106/15 matches; Contributors (16 entries) matches; first-time ['dacacioa']
     citations outside the range: #4764, #4861, #5468, #6192, #6226, #6271, #6332 (RFCs), #6238, #6350 (issues)
PASS code fences balance / no duplicated table headers or doubled `---` (README, ROADMAP, new section)
PASS every local link in README.md and 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
PASS Recently shipped intro names the current cycle (v26.10.x)
FAIL only the four current-version lines lose the `26.10.1` string outside CHANGELOG
     historical mentions must stay: "label": "26.10.1"
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 (the docs snapshot)
PASS git diff --check
```

- **The one FAIL is the snapshot's version label, not a historical
mention.** `docs/user/stable-version.json`'s `"label"` is the docs
site's current Stable version, and `snapshot_stable.py` rewrites it by
design. The validator predates #6286 and knows only the four
current-version lines. Run on the six-file commit alone, the same check
passes (29 checks, 0 FAIL, 0 WARN).
- The docs build passes: `npm ci && npm run build` in `docs/user`. `node
tools/docs/test_log_rules.mjs` reports 707 checks passed for 45 rules.
The line moves it reports were already on `main`.
- Four merged PRs (#6340, #6341, #6364, #6372) named screenshots to
recapture after merge (`psk-reporter-sidebar.png`, `menu-tools.png`,
`connect-dialog.png`, `rx-agc-calibration.png`), and #6386 named the
panadapter shots (`panadapter.png` and the main-window captures, which
still show the old title strip). They were not recaptured, so the Stable
snapshot carries the older images.
- No configure, build or test was run for this prep; it is metadata
only, and CI builds it.
- `main` CI at the cutoff `46943eb3c`: Docs and CodeQL - Code Quality
are green; CI, Full Suite, CodeQL and the System Libraries Canary were
still running when this was opened. At `414082197`, every one of them
was green.

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 (1M context) <noreply@anthropic.com>
ten9876 added a commit that referenced this pull request Oct 12, 2026
… 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>
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.

2 participants