[codex] Consolidate profile load hold timing#3573
Conversation
0e603f3 to
c4263b3
Compare
c4263b3 to
b731615
Compare
There was a problem hiding this comment.
Pull request overview
This PR centralizes profile-load “radio-state write hold” timing into the shared profile-load command helper so that dependent recovery timers (deferred pan-dimension flush and post-hold recovery) stay derived from the same source of truth, reducing duplicated magic delays across model/UI code.
Changes:
- Hoists
kProfileLoadStateWriteHoldMs(and derived recovery delays) intoProfileLoadCommand.hand updates call sites to use the shared constants. - Replaces the hard-coded suppressed-write callback code with a named sentinel constant.
- Extends
profile_load_command_testto assert the derived recovery delays are ordered after the hold window.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tests/profile_load_command_test.cpp | Adds assertions ensuring recovery delays remain ordered relative to the shared hold duration. |
| src/models/RadioModel.cpp | Uses the named suppressed-write sentinel (and removes the local duplicated hold constant). |
| src/models/ProfileLoadCommand.h | Introduces shared profile-load timing constants and a named suppressed-command sentinel. |
| src/gui/MainWindow_Wiring.cpp | Removes duplicated hold constants and derives delayed recovery singleShot timings from shared constants. |
There was a problem hiding this comment.
Reviewed the diff against the branch — this is a clean, well-scoped consolidation. Thanks @rfoust. 🙏
What's good
- The hold window is now a single source of truth.
kProfileLoadStateWriteHoldMsmoves intoProfileLoadCommand.h, and the local copies in bothRadioModel.cppandMainWindow_Wiring.cpp(two functions) are removed. Both consumers already#include "ProfileLoadCommand.h", so the shared constant resolves cleanly. - Behavior is preserved exactly: the derived timers reproduce the old magic delays (
10000 + 1000 = 11000,10000 + 1250 = 11250), so the twoQTimer::singleShotcalls are unchanged at runtime while no longer being free-floating literals. - The
static_cast<int>on theqint64base is safe at these magnitudes (~11k, far underINT_MAX), andQTimer::singleShottakesint, so the narrowing is correct. - Naming
kProfileLoadSuppressedCommandCodeand documenting that it is not a SmartSDR protocol code is a genuine readability win at the suppression site. - The two new test invariants (
deferred pan flush > hold,post-hold recovery > deferred flush) lock in the ordering relationship, so a future edit to the base hold can't silently invert the sequence. Nice touch.
On the Copilot finding (RadioModel.cpp:3654) — the concern that the suppressed-write path invokes cb(...) with a non-zero code while returning 0, which callbacks may misread as a real radio error, is a legitimate observation. But it's pre-existing behavior: this PR only renames the literal and adds a clarifying comment; it doesn't alter the callback contract. So it's not a blocker for this consolidation. If you agree it's worth addressing, it'd be cleaner as a focused follow-up — either report suppression as a benign completion (cb(0, ...)) or skip the callback entirely — after confirming what the registered callbacks actually do with code != 0.
No scope creep, no AppSettings/RAII concerns, includes are correct. Looks good to me.
🤖 aethersdr-agent · cost: $2.4616 · model: claude-opus-4-8
ten9876
left a comment
There was a problem hiding this comment.
Approving — clean, behavior-preserving consolidation with good test coverage. Verified the values are exactly preserved and nothing else needed touching.
Correctness — verified equivalent
- Values preserved exactly:
kProfileLoadDeferredPanFlushDelayMs = hold+1000 = 11000andkProfileLoadPostHoldRecoveryDelayMs = hold+1250 = 11250reproduce the old literals precisely; the sentinel0x50000061is unchanged. No timing/behavior drift. inline constexprin the header is the right tool — ODR-safe across TUs (C++17 inline variables), correctly replacing the three duplicated localconstexprdefs.#include <QtGlobal>correctly added forqint64, andstatic_cast<int>(10000+1000)is well withinintrange forQTimer::singleShot.- Include dependency satisfied: both consuming TUs (
RadioModel.cpp,MainWindow_Wiring.cpp) already includeProfileLoadCommand.h, so removing the local defs is safe. - Sentinel scope:
0x50000061is only ever produced (passed to a callback insendCmd), never compared against elsewhere — so naming it is a pure readability win at the one producer, with no missed consumer. The "not a SmartSDR protocol response code" comment is genuinely useful, since0x5…reads like a protocol code.
Tests
The two new checks lock the ordering invariant (deferredPanFlush > writeHold, postHoldRecovery > deferredPanFlush) — exactly the right thing to pin, so future edits to the hold window keep the derived timers correctly ordered. 👍
Minor / optional (non-blocking)
- The
3500ms early-flush literal inscheduleProfileLoadRecoveryis left un-consolidated — defensible, since it's a best-effort early flush with different semantics from the hold-derived timers, not a copy of the hold value. Only noting it given the PR's consolidation theme.
Good centralization around the radio-authoritative profile-load path (Principle I). 🚀
Summary
Fixes #3572. This follow-up consolidates the profile-load radio-state write hold into the shared profile-load command helper, derives the delayed pan-dimension flush and post-hold recovery timers from that same hold window, and names the internal suppressed-write sentinel so it is clear the callback is not reporting a SmartSDR protocol response. The goal is to keep AetherSDR from sending profile-owned slice/panadapter state back to the radio while the radio is still restoring its saved profile/session layout.
Constitution principle honored
Principle I - FlexLib Is The Protocol Authority. The change keeps profile recall radio-state timing centralized around the radio-authoritative profile-load path instead of scattering independent magic delays through the client.
Test plan
cmake --build build --target profile_load_command_test AetherSDR aprs_packet_test aprs_messenger_test -j4)Local validation run:
ctest --test-dir build --output-on-failure -R 'profile_load_command_test|profile_transfer_test|radio_status_ownership_test|slice_recreate_policy_test|transmit_model_test'ctest --test-dir build --output-on-failure -E '^theme_manager_test$'git diff --checkChecklist
docs/COMMIT-SIGNING.md)AppSettingscalls - use nested-JSON-under-one-key (Principle V)MeterSmoother(Principle II)