Skip to content

[codex] Fix VFO flag-side pan following#3866

Merged
ten9876 merged 2 commits into
aethersdr:mainfrom
rfoust:codex/fix-3840-vfo-flag-side
Jun 27, 2026
Merged

[codex] Fix VFO flag-side pan following#3866
ten9876 merged 2 commits into
aethersdr:mainfrom
rfoust:codex/fix-3840-vfo-flag-side

Conversation

@rfoust

@rfoust rfoust commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #3840. Pan Follows VFO now predicts the side a slice flag will occupy after the next tune step instead of using the stale current flag side. That prevents normal wheel tuning near a pan edge from forcing a pan recenter when the flag can flip inward and remain fully visible.

This also adds deterministic multi-VFO flag placement helpers, keeps mode-specific split and RTTY defaults, and extends the agent automation bridge with VFO targeting, wheel events, and grab pan-visible <index> for operator-visible pan screenshots that include VFO flag overlays.

Root Cause

The pan-follow reveal guard used the current VfoWidget::onLeft() value before VfoWidget::updatePosition() had a chance to recompute the flag side for the new frequency. When the next frequency would make the flag flip inward, the guard still treated the old outward side as authoritative and recentered the pan unnecessarily.

Constitution principle honored

Principle VIII - Evidence Over Assertion. The placement logic has a focused helper test, and the behavior was verified with the visible agent automation bridge against a real FLEX-6700 without enabling TX automation.

Test plan

  • Local build passes: cmake --build build --target AetherSDR vfo_flag_placement_test -j16
  • Targeted test passes: ctest --test-dir build -R ^vfo_flag_placement_test$ --output-on-failure
  • Diff whitespace check passes: git diff --check
  • Automation probe wrapper syntax check passes: python3 -m py_compile tools/automation_probe.py
  • Accessibility checker run: python3 tools/check_a11y.py exits 0 with existing warning-only findings
  • Behavior verified on a real radio through the visible agent automation bridge

Bridge evidence:

  • Target slice: 1
  • Target pan index: 1
  • Target pan id: 0x40000001
  • Same-pan slices: [1, 2]
  • Original/restored slice 1 frequency: 14.2196 MHz
  • Wheel-tuned frequency: 14.2184 MHz
  • Target pan center stayed at 14.236363 MHz
  • TX before/after/end: false / false / false
  • grab pan-visible 1 returned class: PanadapterApplet, surfaceClass: SpectrumWidget, 762x724
  • Raw grab pan 1 still returned class: SpectrumWidget, 762x708

Checklist

  • Commits are signed
  • No new flat-key AppSettings calls
  • Code is clean-room
  • All meter UI uses MeterSmoother or is not meter UI
  • Documentation updated for new automation bridge behavior
  • Security-sensitive changes reference a GHSA if applicable: N/A

Generated with OpenAI Codex.

@rfoust rfoust marked this pull request as ready for review June 27, 2026 15:41
@rfoust rfoust requested review from a team as code owners June 27, 2026 15:41
Copilot AI review requested due to automatic review settings June 27, 2026 15:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes pan-follow behavior during incremental VFO tuning near pan edges by predicting the VFO flag side after the next tune step (rather than using the stale current side), preventing unnecessary pan recentering when the flag would flip inward and remain visible. It also adds deterministic VFO flag placement helpers + a focused test, and extends the automation bridge with pan-visible grabs and VFO targeting/wheel events for UI verification workflows.

Changes:

  • Predict flag-side placement for the new frequency in panFollowVfo(), using new Spectrum/VFO helper logic.
  • Add deterministic flag placement helpers (single + multi-VFO) and a dedicated vfo_flag_placement_test.
  • Extend the automation bridge + probe script + docs with grab pan-visible, VFO selectors, and invoke ... wheel.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tools/automation_probe.py Adds CLI support for grab pan-visible <index> / pan-composite selectors.
tests/vfo_flag_placement_test.cpp New deterministic test covering mode defaults + edge/hysteresis and multi-VFO placement decisions.
src/gui/VfoWidget.h Introduces reusable, deterministic helpers for default flag side and auto-direction decisions.
src/gui/VfoWidget.cpp Reuses the new mode-default helper; surfaces sliceId as a widget property for automation targeting.
src/gui/SpectrumWidget.h Adds vfoFlagOnLeftForSlice(...) API to predict flag side for a given slice/frequency.
src/gui/SpectrumWidget.cpp Implements predicted-flag-side logic and refactors multi-VFO direction logic into helpers used by paint/reposition paths.
src/gui/MainWindow_Wiring.cpp Switches pan-follow offset selection to predicted flag side for the new frequency.
src/core/AutomationServer.h Documents new grab pan-visible verb in the bridge header docs.
src/core/AutomationServer.cpp Adds grab pan-visible, VFO selectors (vfo <panIndex>, vfo slice <id>), and invoke ... wheel.
docs/automation-bridge.md Documents grab pan-visible, VFO selector resolution order update, and wheel invoke action.
CMakeLists.txt Registers vfo_flag_placement_test with CTest.

Comment on lines +444 to +460
QWidget* vfoWidgetForPanIndex(int index)
{
const QList<QWidget*> vfos = findWidgetsByClass(QStringLiteral("VfoWidget"));
for (QWidget* vfo : vfos) {
for (QWidget* a = vfo; a; a = a->parentWidget()) {
if (shortClassName(a) != QLatin1String("SpectrumWidget")) {
continue;
}
const QVariant pi = a->property("panIndex");
if (pi.isValid() && pi.toInt() == index) {
return vfo;
}
break;
}
}
return nullptr;
}

@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.

Thanks for this, @rfoust — nicely done. The core fix is exactly right: predicting the flag side for the new frequency via vfoFlagOnLeftForSlice(sliceId, mhz, ...) instead of reading the stale vfo->onLeft() is the correct way to stop wheel tuning from forcing a spurious recenter when the flag can flip inward. Pulling the placement decision into the static autoDirectionForSingleFlag / autoDirectionForDeconflictedFlag / defaultFlagOnLeftForMode helpers and reusing them across repositionVfoFlags, paintEvent, and the pan-follow predictor is a clean de-duplication of what was previously three copies of the same branchy logic — and it makes the behavior unit-testable, which the new vfo_flag_placement_test covers well (edge guards + hysteresis hold/return on both sides). CI is green across build/macos/windows/CodeQL/a11y.

A couple of minor, non-blocking notes:

1. vfoWidgetForPanIndex() doesn't prefer a visible match (confirming Copilot's finding). This one is valid. panSpectrumWidgetForIndex() and vfoWidgetForSliceId() both keep scanning and prefer a visible widget when duplicate indices/ids transiently coexist (mid float/dock reparent), but vfoWidgetForPanIndex() returns on the first panIndex match regardless of visibility:

if (pi.isValid() && pi.toInt() == index) {
    return vfo;   // first match wins, even if hidden
}

For consistency with its siblings — and to avoid invoke vfo <panIndex> wheel silently no-op'ing against a hidden flag during a reparent — consider tracking a candidate and preferring isVisible(), same shape as the other two helpers. Low severity since it's only the transient-duplicate window, but the sibling helpers already established the pattern.

2. Scope. The flag-side fix itself is small; the bulk of the diff is the automation-bridge surface (grab pan-visible/pan-composite, the wheel invoke verb, the vfo slice N / vfo N selector shortcuts, sliceId property exposure, docs, and automation_probe.py). The PR body explains these were the tooling used to verify the fix on a real radio, which is reasonable and they're internally coherent and well-documented — just flagging that a reviewer skimming the title may not expect a feature bundle. Not asking you to split it; just noting it.

Both points are minor — the fix and refactor are solid as-is.


🤖 aethersdr-agent · cost: $2.9976 · model: claude-opus-4-8

@rfoust rfoust self-assigned this Jun 27, 2026
@rfoust rfoust force-pushed the codex/fix-3840-vfo-flag-side branch from 576d2b8 to 73963e7 Compare June 27, 2026 16:36

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved. Reviewed the fix + verified the bridge additions are TX-safe.

  • Core fix correct — the pan-follow reveal guard now predicts the flag side for the new frequency via vfoFlagOnLeftForSlice(sliceId, mhz) instead of the stale vfo->onLeft(), so wheel-tuning near a pan edge no longer forces a spurious recenter when the flag flips inward. Clean de-dup into static, unit-tested helpers (vfo_flag_placement_test: edge guards + hysteresis). Complements #3786's drag-recenter fix.
  • Bridge additions TX-safe — the wheel verb dispatches a QWheelEvent (RX tuning) and is gated by the same isTransmitControl guard that runs first in doInvoke; grab pan-visible/pan-composite are read-only screenshots; vfo selectors are targeting only. Not a keying path (Principle VI N/A). Live-verified on a FLEX-6700 with TX false throughout.

One minor follow-up filed: vfoWidgetForPanIndex() should prefer a visible match like its siblings. CI green on all 6. Thanks @rfoust.

@ten9876 ten9876 merged commit 115c1fd into aethersdr:main Jun 27, 2026
6 checks passed
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.

Pan following vfo prevents full spectrum use when tuning flag changes orientation

3 participants