Repository navigation
RTL: live PPM adjustment on the analog-only stack - #6082
Conversation
There was a problem hiding this comment.
1. Issue fit
Yes, for the increment it claims. There is no Fixes #NNNN; the PR is governed by analog RTL RFC #5468 and is the re-opened replacement for #6003, so I reviewed it against its own stated scope: live per-device PPM adjustment with a 200 ms debounce, immediate Apply, Enter committing the field without closing the parent dialog, requested values kept separate from hardware readback, and newer edits surviving older completion/refusal. Every one of those maps to a hunk, and each has a corresponding assertion in rtl_receiver_settings_widget_test.cpp. No requirement in the body is unaddressed.
Two framing notes, because they change what this review can and cannot claim:
- The base is not
main.5e39790a^is not an ancestor oforigin/main(49 commits off), andsrc/gui/RtlReceiverSettingsWidget.cppand all three tests do not exist onmainat all. GitHub's diff-vs-main therefore carries the whole #5974 stack. I reviewed only the five-file increment at5e39790a; the stack base is #5974's review, not re-reviewed here. - No build or test job ran on this head.
get_check_runs(5e39790a)returns exactly two checks — "Static checks" and "Sanitizer option configures", both green. There is no compile job and nocteston this SHA, and the body is straight about why ("not a separate exact-head rebuild of this checkpoint"; results recorded against #6083). So "CI is green" here means the static gates passed, not that the three modified tests ran. I could not execute them either. Everything below is reasoned from the code in the head checkout, not reproduced at runtime.
Socket tests: none. Grepping the three modified test sources for QTcpServer|QTcpSocket|QUdpSocket|QLocalServer|QWebSocketServer|bind(|listen(|connectToHost|Fake* returns nothing. All three are socket-free and registered in tests/tests.cmake (lines 1426, 1432, 5433), with the widget test pinned to QT_QPA_PLATFORM=offscreen. The widget/model/injected-worker layer is the right one per AGENTS.md's test-layer table for GUI state-machine and refusal behavior.
2. Scope
| File / group | What it changes | Claimed in body? | Verdict |
|---|---|---|---|
src/gui/RtlReceiverSettingsWidget.cpp/.h |
debounce timer, submitPpm(), hideEvent(), RtlPpmSpinBox Return override, setAutoDefault(false), explanation text |
Yes | In scope |
tests/rtl_receiver_settings_widget_test.cpp |
real-keyboard + dialog-host fixture, burst/arrow/typed/refusal/hide/rebind/synchronous cases | Yes | In scope |
tests/rtl_capture_worker_test.cpp |
PPM preserves capture geometry; stale-token spectrum rejected | Yes ("injected worker/model checks") | In scope |
tests/rtl_device_controls_test.cpp |
PPM preserves capture identity / receiver / RF view plus a rework of the shared fixture (Fm→Fmn, filters ±8000→-6500/+7500, and newly audioMute=true, squelchEnabled=true, squelchLevel=43) |
Partly — the view/centre reasoning is in the test comment, the fixture's audio/squelch change is nowhere | Nit — see N4 |
| Stack base (AudioEngine, WDSP, RadioModel, AutomationServer, …) | Not touched by this increment | n/a | Reviewed under #5974 |
No new public surface: no protocol verb, settings key, capability field, CLI flag or CHANGELOG.md entry. No - line removes a guard, early return, confirmation, or a comment naming a fixed symptom — the only deletions are the two lines the debounce replaces (m_ppmEdited = true and the inline Apply lambda), both preserved in submitPpm()/the new lambda. No build config, CI, or vendored-tree churn.
Security. The required CodeGuard audit ran over the flagged file list. Nothing exploitable in this PR's five files, and every flagged category is a false positive on them: no shell, QProcess, URL, QNetworkAccessManager, SQL, HTML surface, credential literal, or non-ASCII byte exists in the diff (0x52544c0000000000ULL at RtlReceiverSettingsWidget.cpp:36 is a request-ID namespace seed, ASCII "RTL", not a key). The api:buffer_ops memcpy signal is against a stale base — there is no memcpy hunk in this PR, and the four copies it points at in RtlRfExtractor.cpp / RadioModel.cpp are each bounded (verified by security-audit). One genuine, pre-existing and out-of-increment item surfaced, credited to security-audit (CodeGuard): src/core/AutomationServer.cpp:2401-2419 creates /tmp/aethersdr-automation{,.json} at predictable, non-random paths with plain QFile::open(WriteOnly|Truncate), which follows symlinks — on a multi-user Linux host a local user can pre-create that path as a symlink and have the next start truncate a victim-owned file. Not caused by, and not fixable in, this PR; worth its own issue.
3. Blockers
None.
4. Nits (all non-blocking)
- N1 — a no-change PPM request still fires while the backend is pending (
RtlReceiverSettingsWidget.cpp:226, inline). Whenconfirmed["pending"]is true, the settled-value early return is skipped, so returning the entry to the already-applied value submits a redundantppm.setand costs the operator a real capture restart and reacquisition. The comment explains the reasoning (only the backend owns the latest-pending transaction) and the body lists this as a disclosed carry-over from #6003 — recording it as disclosed, not re-raising it as new. - N2 — stale-token spectrum check is negative-only and uses a magic literal (
rtl_capture_worker_test.cpp:233, inline). Discriminates today, but has no paired positive control, unlike its sibling at lines 566-571. - N3 — the burst assertion is the one real flake surface (
rtl_receiver_settings_widget_test.cpp:166, inline). Two 40 ms gaps inside a 200 ms single-shot window; a >160 ms scheduler stall makes it fail. This is the #6003 "debounce margins" note, still open. ThepumpFor(260)negative checks elsewhere are not flaky — the timer is stopped on those paths, so they can't race. - N4 — the
rtl_device_controls_testfixture rework deserves a sentence in the body (rtl_device_controls_test.cpp:23, inline). Also, the "nondefault receiver" assertion at line 133 checksaudioGain/audioPan/audioMutebut notsquelchEnabled/squelchLevel, so it would not notice if those two silently reverted to defaults. - N5 — a refused PPM value survives
hideEventas an "edit" (RtlReceiverSettingsWidget.cpp:212, inline). The comment says "discard only unsent input"; a refused value is neither accepted nor discarded, andm_ppmEditedstays true soacceptStatus()will not reseed the entry for the rest of the binding. - N6 — the new status line is never announced.
renderStatus()setsm_status->setAccessibleDescription(...)but fires noQAccessible::updateAccessibility, so "Frequency correction will apply after this adjustment." reaches a screen-reader user only if focus happens to land on the label. This is the widget's pre-existing pattern anddocs/a11y.md:119's throttle rule argues against spamming an announcement per arrow press, so I am not asking for a change — noting it as a known coverage edge.
5. What I tried to break, and it held
- The Return override is load-bearing, and the test fixture mirrors the real host. I assumed the
RtlPpmSpinBoxsubclass was defensive dead code and went looking for the actual host:RadioSetupDialog.cpp:856creates the page, and that dialog is aPersistentDialogwithQDialogButtonBox(QDialogButtonBox::Close)at line 1039 wiredrejected → QDialog::closeat 1043 — exactly the fixture the test builds.QAbstractSpinBox::keyPressEventdeliberately callsevent->ignore()on Return so the dialog's default button fires, so without this override Enter would close Radio Setup mid-edit. The base call precedes theaccept(), so the field still commits.apply->setAutoDefault(false)closes the complementary hole (dialog push-buttons default toautoDefault=true). - Self-triggered debounce loops. I checked every programmatic write to
m_ppm—bindBackend(),acceptStatus(),hideEvent()— and all three are underQSignalBlocker, so no model-driven or revert-drivensetValuecan start a timer or spoof an operator edit. ThevalueChangedguard adds!isVisible() || !m_haveStateon top. - Synchronous reentrancy.
submit()assignspending = nextRequest()beforeinvokeBackendExtension, so a backend that completes on the same stack (the test'ssynchronousPpm) is seen withid == m_ppmRequestrather than being dropped as stale.submitPpm()'s guards are a strict superset ofsubmit()'s, som_submittedPpmcan never go stale relative to a non-zerom_ppmRequest. - The squelch-enabled fixture.
audioMute=true, squelchEnabled=true, squelchLevel=43looked like it should be refused:RtlReceivePipeline::prepareDetailedrejectssquelchEnabledforWfm(line 63) and for anylegacyreceiver (line 81).Fmnis neither — it is explicitly excluded from thelegacypredicate — andsquelchLevel=43is inside[0,100], so the fixture is admitted and the pre-existing DC/display assertions still exercise a live receiver. I also confirmed the aggregate-initialiser field order againstRtlCaptureTransaction.h:26-36; the positional values land on the fields the assertion names. - Lifecycle and stale-completion paths. Disconnect, capability loss, capability restore, and backend rebind all route through
bindBackend(), which stops the timer, zeroes all three request IDs and clearsm_haveState— so a timer cannot outlive its session, andsubmitPpm()re-validates connection, backend identity andsupportsSettings()independently before sending. Old-generationextensionResult/extensionErrorare gated onm_bindingGenerationandsource == m_model.backend(). - Hostile device text. All three labels are forced to
Qt::PlainText(lines 75-78), so a device-reportedserial, asaveReason, or a refusalreasoncannot inject Qt rich text into the GUI. requestedPpm. The new fixture key is not invented —RtlSdrBackend.cpp:1763really emits it. The widget not reading it is fine.
Could not check: compile and test execution (no build here, and none in CI on this SHA); the audible reacquisition cost of one auto-applied correction; and whether m_viewport is guaranteed engaged at the first Access::view(backend) call — *backend.m_viewport on an empty std::optional would be UB rather than a clean failure, though the preceding setPanBandwidth/setPanCenter calls make it very likely engaged.
6. Recommendation
Needs maintainer decision, on one axis only. The code is in good shape — I went after the debounce state machine from the directions that usually break this pattern (reentrancy, stale completion, programmatic writes, teardown, the second session) and it survived all of them, with tests that are mostly genuine rather than self-affirming. The open question is the one the author raised themselves: "Apply-on-edit is this PR's specific UX decision." Replacing explicit-Apply with auto-apply-after-200 ms on a control that triggers a real hardware recapture is a behavior change to an existing (if unmerged) control, and per GOVERNANCE.md that is a maintainer's call, not a reviewer's — I am not treating it as a smuggled preference, since it is the PR's stated purpose under RFC #5468, but it does want an explicit ruling.
Concrete next step: a maintainer rules on apply-on-edit; the author optionally picks up N2-N4 (one sentence in the body for the fixture rework, the named kSpectrumBinCount constant plus a positive control for the stale-token check, and a wider margin on the burst assertion). Given that no compile or ctest ran on 5e39790a, I would want the focused suite green at this exact head — not only at the #6083 aggregate — before merge.
🤖 aethersdr-agent · cost: $17.5346 · model: claude-opus-5
ten9876
left a comment
There was a problem hiding this comment.
The five-file increment at d4ef62d54a91fffcbfc262fa182a2ee08cfc46a2 matches #5468's live PPM scope: it changes the settings-page submission policy while retaining the existing accepted hardware transaction and per-device persistence. I found no implementation blocker. The remaining decision is the explicitly proposed apply-on-edit UX, not a new request to approve the already-ratified backend seam or Digital FM.
Scope
| Files/group | Claimed? | Verdict |
|---|---|---|
RtlReceiverSettingsWidget.{cpp,h} |
Yes: 200 ms debounce, Enter, hide/rebind handling | In scope; deliberate UX change |
rtl_receiver_settings_widget_test.cpp |
Yes: keyboard, coalescing, lifecycle and stale completion | In scope; socket-free model injection |
rtl_device_controls_test.cpp |
Yes: preserve receiver/RF/view and accepted persistence | In scope; injected USB boundary, no device opened |
rtl_capture_worker_test.cpp |
Yes: generation and stale-spectrum regression strengthening | In scope |
No additional USB path, thread, calibration default or Digital dependency is introduced. Older stack commits are prerequisites, not unrelated additions to this five-file increment.
Blockers
None found.
Non-blocking notes
src/gui/RtlReceiverSettingsWidget.cpp:205: hiding cancels unsent input, including a just-committed edit whose debounce has not fired; an already-submitted transaction may still finish and persist. This is consistent with the body and was exercised by the test. Jeremy's UX decision should cover this exact boundary, including window-system hides; it should not be described as “close always applies the visible value.”tests/tests.cmake:5795: exact-head hosted results currently show Static Checks, not all three application/platform builds. Earliest run creation 2026-10-03 04:56:21 UTC predates current main 2026-10-04 18:26:40 UTC; branch protection'sstrict: falsedoes not force an updated merge test. The historical Mac/Nobara qualifications remain historical.
Verification and attempted failures
- Built the head's focused native targets in an isolated worktree (GCC 16.2.1 / Qt 6.12).
rtl_receiver_settings_widget_testpasses offscreen in 6.34 s, andrtl_device_controls_testpasses in 1.57 s. Test profiles and FFTW wisdom are isolated; no actual USB device or network peer is opened. - The widget test drives real arrow/Return events and covers burst coalescing, incomplete typed text, immediate Apply, reversal during pending work, older completion/refusal, explicit retry, hide/reopen, destruction, disconnect, backend replacement, capability loss and synchronous completion. The hardware-injection test preserves asymmetric FM-N filters, gain/pan/mute/SQL and an independent viewport across adoption/rollback; rejected and pending values do not become saved calibration.
- Disconnected the timer-to-submission binding while retaining manual Apply. The widget regression fails its automatic burst/arrow/Enter/newer-edit assertions (CTest exit 8). Restored exact source, rebuilt, and it passes again in 6.20 s.
rtl_capture_worker_testalso passes in 10.26 s, including old-token rejection, the paired valid-current-token control and subsequent production-worker frame. All three changed test targets were therefore built and passed. - Source audit follows generation/request-id guards, signal blockers and accepted/readback separation. Existing RTL typed extension submission is retained. Four boundary checks pass against parent and head with identical findings/counts and current manifests.
Did not drive an application bridge session: SimBackend has no RTL calibration extension or USB capture-reacquisition path, so a demo session cannot establish this PR's live PPM claims. The actual page is exercised by the offscreen widget test. No fresh antenna/calibration-sign/reacquisition-timing or native Windows/macOS qualification is claimed. The separate automated code-review skill was unavailable.
Recommendation: Needs maintainer decision on the disclosed apply-on-edit interaction. The tested implementation supports that interaction; if accepted, land in stack order after current integration checks, without reopening the earlier seam decisions.
— Jeremy KK7GWY + Codex
ten9876
left a comment
There was a problem hiding this comment.
Issue fit: yes, for the five-file increment. Live per-device PPM edits now apply after a 200 ms debounce or immediately on Apply. The requested value stays separate from hardware readback and from accepted persistence, as #5468 requires. I found no code blocker in this increment. The open item is the apply-on-edit UX, which is the maintainer decision, plus the stack rebase this PR inherits. I reviewed e3464c91..d4ef62d5 (this PR's own base..head) at head d4ef62d54a91fffcbfc262fa182a2ee08cfc46a2, as an independent second pass after the earlier ten9876 + Codex review on the same head.
Stack: main ← #5973 ← #5974 ← #6082 ← #6083. The base is #5974's head branch. The codex/rtl-live-ppm branch was previously the head of #6003, which merged into codex/rtl-native-hd-fm, not main. This PR replaces it, as the body says.
Scope
| Files | Claimed? | Verdict |
|---|---|---|
src/gui/RtlReceiverSettingsWidget.{h,cpp} |
Yes: debounce, Enter handling, hide/rebind | In scope. This is a deliberate UX change. |
tests/rtl_receiver_settings_widget_test.cpp, rtl_device_controls_test.cpp, rtl_capture_worker_test.cpp |
Yes | In scope. All three are socket-free (model or USB-boundary injection; nothing opens, binds or connects). |
Everything in the diff is explained by the body. No CHANGELOG entry.
Blockers
None of its own.
Inherited: this branch must be rebuilt on the rebased #5973 and #5974. Every head in the stack descends from the pre-squash #5924 snapshot and lacks fe91a982f (SQL/FM-filter rules for non-RTL radios), which is now on main. See #5973's review. The five files here don't touch that code, so the rebase should be mechanical for this increment.
For the maintainer (@ten9876)
- Apply-on-edit for a hardware calibration value. The earlier review routed this to you. One boundary is worth stating precisely.
hideEvent(RtlReceiverSettingsWidget.cpp:205-214) cancels unsent input, and in Qt that includes a page-switch inside Radio Setup and a window-system minimise, not just Close. A typed value committed with Enter and then hidden within 200 ms is therefore discarded. The help text says "Closing this window does not undo an applied correction". That is true, but it doesn't tell the operator that an unapplied one is dropped.
Non-blocking
- The new help text describes "focus change" as a commit trigger. With
keyboardTracking(false), a typed value commits on focus-out, andvalueChangedthen starts the timer. If focus leaves because the page is being hidden, the hide handler stops the timer first. That matches the cancel semantics above; the help text could say "unless the window is closed first".
What I verified, and what I tried to break
- Re-checked the prior review. The scope is five files, and no new USB path, thread or calibration default is introduced. Both hold against the diff.
- Refusal path. A refused request stops the debounce only when the spin box still shows the refused value (
m_ppm->value() == m_submittedPpm), so a newer edit keeps its deadline and an old refusal can't swallow it. - Re-submitting a pending value.
submitPpmreturns without a new request when the value equals the in-flight one, and without any request when it equals the confirmed value with nothing pending. - Stale bindings.
bindBackendstops the timer before rebinding.submitPpmre-checksisVisible(),m_haveState, connection, backend identity and capability at fire time, so a timer that outlives a disconnect or a backend swap does nothing. - Enter key.
RtlPpmSpinBox::keyPressEventletsQSpinBoxinterpret the text first, then accepts Return/Enter so the dialog's default button doesn't fire.apply->setAutoDefault(false)closes the other route. - a11y. The spin box is a
QFormLayoutrow with a label, so its accessible name comes from the label buddy. No new unlabeled interactive widget. - Not driven in the app. The demo backend has no RTL calibration extension and no USB re-acquisition path, so a demo session can't reach this page's behaviour. I relied on the PR's offscreen widget test (which the earlier review mutation-checked) and on source reading. Nothing was run against hardware.
Recommendation: Needs maintainer decision on apply-on-edit. If accepted, land it in stack order after #5973 and #5974 are rebased onto current main.
— Jeremy KK7GWY + Claude
e3464c9 to
b8aba26
Compare
1e0276f to
c667b41
Compare
b8aba26 to
ebb35cf
Compare
jensenpat
left a comment
There was a problem hiding this comment.
Verdict
Do not merge this head. No code defect in the live-PPM increment. The branch cannot land: it is CONFLICTING / DIRTY against current main (121340c08fb5), and required build, check-macos, and check-windows have no runs on this SHA.
Reviewed c667b41cb8e6f825445954fcaaaf7eaca31229f5. Issue fit: the five-file increment matches the live per-device PPM behavior described for analog RTL RFC #5468 (200 ms debounce or immediate Apply, requested value separate from readback and from accepted persistence, hide/minimize/page-switch cancels unsent edits). No linked closing issue. No CHANGELOG entry.
Parent commits 1398f3b9 and ebb35cfd replay the WFM stack that already squash-merged as #5973 (ca4e7cb0). They are not ancestors of main. The GitHub three-dot diff is still 90 files because the merge-base is 5f09371e, thirteen commits behind main.
Scope
| Group | What it is | Verdict |
|---|---|---|
RtlReceiverSettingsWidget.{h,cpp} |
Debounce, Enter-does-not-close, hide cancels unsent PPM | In scope. Matches the help text and the widget test. |
rtl_receiver_settings_widget_test.cpp, rtl_device_controls_test.cpp, rtl_capture_worker_test.cpp |
Widget lifecycle, preservation across PPM adopt/rollback, stale-spectrum token | In scope. No socket, bind, listen, or connectToHost. Device test injects a USB boundary and does not open a device. |
| Other 85 files in the three-dot diff | WFM DSP, applet, typed receive, WDSP patch, manifests | Already on main via #5973. Not a second copy to land from this head. |
Shared SliceDelta.h |
WFM fields from this branch, noise-blanker fields from main |
Only content conflict. Keep both: NoiseBlankerKind.h from main, WFM fields already on main. |
A scratch merge of this head onto origin/main (121340c08fb5) changes seven paths: the five PPM files, conflicted SliceDelta.h, and docs/architecture/aetherd-touchpoints.md. That manifest hunk moves core/AppSettings.h from 107 to 108. AppSettings.h itself does not change versus main, and the #5973 squash left the count at 107. Merging this head would publish a stale generated count. Rebase onto current main and regenerate; do not hand-edit the number on this base. Static checks passed for this tree on 2026-10-06, which does not validate the count after merging later main.
RTL is the only family whose behavior changes in the increment. Flex, HL2, Icom, ANAN, and Sim sources are unchanged versus current main after the scratch merge. The conflict is an include on the shared slice delta, not a new cross-family PPM path.
Findings
No demonstrated defect in the PPM widget or its tests.
submitPpm keeps a distinct in-flight value, ignores a duplicate of that value, and skips a no-op only when nothing is pending and the spin box already shows confirmed PPM. A refusal stops the timer only when the spin box still shows the refused value, so a newer edit keeps its deadline. hideEvent stops the timer and restores confirmed PPM unless a request is in flight, in which case it shows the submitted value and still allows that transaction to finish. Enter is accepted on the spin box and Apply is not an auto-default, so Return does not hit the dialog Close button. bindBackend stops the timer before rebinding. Those are the paths the new widget test drives, including burst-without-yield, arrow keys, uncommitted typed digits, reversal during an in-flight request, hide/show, disconnect, capability loss, backend replacement, synchronous completion, and destruction.
Apply-on-edit is the behavior this PR specifies, including discarding an Enter that has not yet debounced when the page hides. That matches the help text. I am not reopening it as a new UX defect.
Gates
mergeable: CONFLICTING,mergeStateStatus: DIRTY,reviewDecision: REVIEW_REQUIRED.- Head checks: Static checks and Sanitizer option configures succeeded (a duplicate pair was cancelled). No
build,check-macos, orcheck-windowsrun exists forc667b41c. - Five unresolved review threads remain, all non-blocking nits from aethersdr-agent on the October 2 review. Two are outdated. Conversations still have to be resolved before a later merge.
- Commits are GitHub-verified SSH signatures (
verified: true).
Verification limits
Read the five-file increment (ebb35cfd..c667b41c, +409/−20) and reproduced the merge against origin/main. Did not build or run tests: the conflict and missing required checks already block merge, and a green local run would not make this head mergeable. No app launch. No fresh hardware PPM reacquisition.
Non-blocking follow-up, not a merge prerequisite: the body says a refusal that arrives after hide, while a request was still pending, keeps the submitted value for an explicit retry. The widget test covers hide-after-refusal and confirm-while-hidden. It does not drive refuse-after-hide. The error path leaves m_ppmEdited set, which is what produces that result.
c667b41 to
ed01fdb
Compare
jensenpat
left a comment
There was a problem hiding this comment.
Rebased onto current main 43e2633e9. The resulting diff is the five-file live PPM increment already reviewed at c667b41c (RtlReceiverSettingsWidget plus the three RTL tests, +409/−20). Cherry-pick applied cleanly; author remains Ozy311. No touchpoint or SliceDelta change remains.
Approving this SHA. Merge still waits on required checks and resolved review threads.
… Principle VIII. An audit of every page against the 106 PRs in v26.10.2, each fact checked against the source at the release commit: - Menu paths: AetherMap, AetherModem, Callsign Lookup and FreeDV Reporter are on View, and Waveforms is on Tools. - New coverage: Windows Firewall rules and the Network Diagnostics page (#6289), live CW contacts (#6268), NB2 on the HL2 and ANAN-G2 (#5966), ANAN-G2 AGC-T and per-radio memory (#6282, #6129), HL2 state restore and its level changes (#6259, #6254, #5975), DVK import (#6247, #6262, #6251), Copy Assist from unprocessed audio (#6216), keyboard-operable status-bar indicators (#6267), the KPA1500 (#5905), CTR2 extensions and the AetherKnob (#6338), live PPM (#6082), and Icom COMP and the squelch scale (#6174, #6184). - Known issues whose GitHub issues are closed are removed. - The style guide now asks for US spelling, and every page follows it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… and re-take the Stable snapshot. Principle VIII. (#6396) ## Summary This brings the user guide (docs.aethersdr.com) up to date with everything in v26.10.2 and re-takes the Stable snapshot so the default site shows it. Docs only, apart from the TCI page prose in `tools/docs/gen_reference.py`; no application source, test or CI changes. Every page was audited against the 106 PRs in the release. Every fact that was added or kept was checked against the source at `f753dbfc4`, the v26.10.2 release commit, not taken from the PR bodies alone. ## What changed - **Menu paths.** - AetherModem, Callsign Lookup, AetherMap and FreeDV Reporter are on **Tools**. They are created on View and then moved to Tools (`MainWindow_Menus.cpp:1519-1520`, `:1569-1575`); the style guide's example says **Tools → AetherMap...**. - **Show live CW contacts** is on **View**. - Waveforms is on **Tools**, not File. - The VUDU applet button was called TXDSP. - Labels now match the source's `...` vs `…`. - **Gaps from this cycle.** - **Windows and Network Diagnostics:** Windows Firewall rules and the Network Diagnostics Windows Firewall page (#6289); the `aether.network` and `aether.nvafx` log categories (#6289, #6308). - **Remote access:** Tailscale shim revocation semantics (#6325). - **Sleep, App Nap and power throttling (#6195):** per-OS notes. - **Hermes-Lite 2:** state restore, RX audio at unity, CW at TUNE level, multi-interface discovery and a discovery troubleshooting entry (#6259, #6254, #5975, #6009, #6293). - **ANAN-G2:** AGC-T range, a "What is remembered" section, and supply voltage and link status (#6282, #6129, #6137, #6211). - **NB2** on the HL2 and ANAN-G2 (#5966). - **DVK:** WAV import requirements, the slot menu without Delete, status text and licensing (#6247, #6262, #6251). - **Copy Assist** from unprocessed audio (#6216). - **Live CW contacts** replace the decoder's contact card (#6268). - **Status-bar indicators** work from the keyboard (#6267). - **Volume:** controllers, hotkeys and TCI `volume` follow the active output (#6129, #6260). - **Peripherals:** the KPA1500 (#5905), CTR2 link extensions and the AetherKnob (#6338), TMate 2 udev (#6250), MIDI RADE Modem and FDV modes (#6031, #6038). - **RTL-SDR:** live PPM (#6082). - **Icom:** COMP and the MK2 squelch scale (#6174, #6184). - **Known issues.** Entries whose GitHub issues are closed are removed (each checked with `gh issue view`). As decided, this includes three closed today without a demonstrated fix: #5107, #4824 and #5665. - **US spelling.** `docs-style-guide.md` asked for British spelling; it now asks for US, by the maintainer's decision, and every page is converted. The generated TCI pages are converted at their source, the prose strings in `tools/docs/gen_reference.py`. Code spans, links and URLs were left alone. UI labels were checked against the source strings, and none of them are British. Two in-page anchors and two Log Analyzer rule anchors (`#bnr-is-grayed-out-…`) follow the renamed headings. - **Stable snapshot.** `snapshot_stable.py 26.10.2` was re-run on the release tree with these corrections, before `main` was merged in, so Stable carries no post-release docs. `docs/` also has `main`'s post-release pages, in US spelling. Stable alone keeps the IC-9700 waterfall-rate entry (#5996): #6402 fixed it after the 26.10.2 tag, so 26.10.2 still has it. The PDF manual already attached to the v26.10.2 release keeps the tag's docs. ## Validation - `npm ci && npm run build` in `docs/user`: success. This is the broken-link and broken-anchor check. - `node tools/docs/test_log_rules.mjs`: 707 checks passed for 45 rules. - `python3 tools/docs/gen_reference.py --check`: up to date (8 pages). - `git diff --check`: clean. - Menu paths on a build of this branch (offscreen, isolated settings, no radio), `menu list`: `Tools → AetherModem...`, `Tools → Callsign Lookup...`, `Tools → AetherMap...`, `Tools → FreeDV Reporter...`, `Tools → Waveforms...`, `View → Show live CW contacts`. ## Screenshots now out of date (not recaptured) These need a recapture pass on the radio: - **Old panadapter title strip (#6386):** `panadapter`, `main-window`, `first-session-*`, `multi-slice-two-slices`, `split-pair`, `tnf-markers`, `offscreen-vfo-indicator`, `pan-3d-stacked-trace`, `default-light-theme`. - **Pre-canon windows (#6334, #6339, #6341, #6372):** `aetherrx*`, `aethertx*`, `aethermodem-aprs`, `bnr-panel`, `connect-dialog`, `connect-by-ip-card`, `connect-icom-rows`, `connect-smartlink-card`, `rx-agc-calibration`. - **Other:** - `title-bar` (lowercase tab states, #6281); - `menu-help` (#6294); - `display-panel` (#6371); - `support-diagnostics-dialog`; - `waveforms-dialog` and `tailnet-remote-access` (show shim 0.4.0); - `about-window` (v26.10.1); - `radio-setup-qrz`. ## Found during the audit, not fixed here - **App string bug:** `TailnetShimDialog.cpp:628` tells the user to "Connect remotely with Connect → Manual"; the real path is **File → Connect to Radio... → Connect by IP**. - **ANAN AGC-T tooltips:** the RX applet and VFO tooltips show the slider position as dB, which is wrong on ANAN. #6282 lists this as a follow-up. - **MIDI Master Volume on non-Flex radios:** it is refused while PC Audio is off, whereas hotkeys, FlexControl and HID drive the line out. The docs describe the code as it is. - **RTL mode names:** `rtl-sdr.md` writes "FM-N" and "CW-R", but the source strings are `FMN` and `CWR`. Left as is until someone confirms what the mode menu displays. - **IC-705 COMP readback:** inferred from the MK2 (#6174) and not verified on an IC-705. - **Not yet documented:** the #6230 Radio Health row "Forward responses skipped", and the Store/MSIX package. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rebased on current main — 2026-10-06
Current head:
c667b41cb8e6f825445954fcaaaf7eaca31229f5. Base stack starts at main5f09371e25ffc032666d36f0ea265dc506621066; parent PR/branch is unchanged. This is a signed replay of the existing increment. It preserves main's unified window chrome and release debug-symbol archives, alongside the RTL dependency packaging and explicit Digital-OFF release flags. Generated touchpoint counts were regenerated from the combined sources.Fresh rebase validation on analog
ca3a36bd: native Apple Silicon / Qt 6.12 / RTL required / Digital OFF application build succeeds. 51/52 selected CTests pass in 226.55 seconds: all 47 RTL regressions and four additional UI checks pass. The new upstreamunified_title_bar_testfails its Alt+F/menu-rounding assertions under Mac offscreen rendering; an independent rebuild and run on clean main5f09371ereproduces the identical two failures. This is an inherited test failure, not a green full-suite claim. Static engine/routing/capability, generated manifests, registration/frozen-gate, theme and build-options checks pass; package-helper tests pass 9/9 and required-RTL configuration checks pass 16 combinations. The Digital increment and vendor trees are unchanged by replay; decoder-ON tests and release packaging were not rerun for these heads. Hosted checks are being refreshed by the push.All validation recorded below predates this rebase and remains evidence for the specific revisions named there. No new live-RF, capacity, or release-package qualification is claimed. Readiness is unchanged; Digital #6002 remains draft.
Previous checkpoint record — 2026-10-05
Replacement for #6003, which GitHub automatically marked merged during ancestry rearrangement without landing it in main. Its original reviews and hardware history remain preserved.
Scope
Five-file live PPM increment under analog RTL RFC #5468, based on repaired WFM #5974. It changes RtlReceiverSettingsWidget and the three focused RTL widget/device/capture tests. No Digital implementation or approval dependency is introduced.
Head:
1e0276fca118df05215c3e7c752be524bb74170e. Parent: #5974.Completed arrow/keyboard edits apply after a 200 ms debounce; Apply submits immediately. Enter commits the field without activating the parent dialog's Close button. Requested values remain separate from hardware readback and accepted per-device persistence. Newer edits survive older completion or refusal.
Hide, page switch, minimize or session change cancels unsent work. This includes Enter followed by hide before the debounce expires; closing does not undo an already submitted adjustment. Hiding an already-refused edit restores confirmed state. A refusal arriving after the page was hidden with a request pending instead retains that submitted/refused value for explicit retry, with applied readback separate. There is no automatic retry. Returning to the applied value while other backend work is pending can deliberately submit a recapture.
Review regression details are retained: the stale-frame test uses the named FFT size and a paired valid-current-token frame; rapid-edit bursts do not pump the event loop between edits; preservation checks use FM-N, asymmetric filtering, gain 37, pan 68, mute and enabled SQL at 43. Comments distinguish cancellation of unsent edits from completion of submitted work.
Validation and decision
Exact checkpoint, native Apple Silicon / Qt 6.12 / optional decoder OFF: all three rebuilt focused tests pass (3/3, 20.92 seconds):
rtl_capture_worker_test,rtl_device_controls_test,rtl_receiver_settings_widget_test. Current worker pacing and the previous named-frame/positive-control, debounce and nondefault SQL regressions are preserved. Static ratchets and generated manifests pass.Earlier
72065d84Mac and7911324aNobara results and operator-reported live adjustment/stability are historical; they are not fresh calibration or reacquisition measurements.Apply-on-edit remains this PR's specific maintainer UX decision. The current review identified no separate blocking code defect in this five-file increment. Existing notes about pending no-change requests and window-system hides remain disclosed. This does not reopen #5924's approved seam or gate the DSP foundation on Digital approval.
Principles II, V/XIV and XI: confirmed state, scoped persistence and demonstrated behavior.
73, Ozy K6OZY · Codex