Context
PR #2905 added LOCKED visual feedback to the VFO and RX applet frequency displays when a tune attempt is blocked by the slice lock. Each widget currently has its own `m_lockedFrequencyTimer` (single-shot, 500ms). When `SliceModel::tuneBlockedByLock()` fires, both widgets start independent timers and both display "LOCKED" simultaneously.
Visually unified (they clear within a frame of each other), but it's two timers and two state bools tracking the same UX concept.
Proposed refactor
Move the feedback timing and "is feedback active" state to `SliceModel` itself, so a single source of truth drives every consumer.
Sketch
```cpp
// SliceModel.h
signals:
void tuneBlockedByLock(); // existing — kept for one-shot reaction
void lockedFeedbackActive(bool on); // new — drives visual state
private:
QTimer m_lockedFeedbackTimer; // 500ms single-shot
bool m_lockedFeedbackActive{false};
```
The two emit signals:
- `tuneBlockedByLock` (existing) — fired on every blocked attempt; consumers that just want a one-shot reaction (e.g. status bar message — see #ISSUE_NEW_2 from this set) connect here.
- `lockedFeedbackActive(bool)` — true on first block, false when 500ms elapses without another. Consumers that want to render a sustained "LOCKED" overlay connect here.
VfoWidget and RxApplet then:
- Drop their per-widget timer + bool
- Connect to `lockedFeedbackActive` and use that as the gate for the LOCKED string in `updateFreqLabel()`
Why this is worth doing
- One state variable for "is feedback active right now" — no risk of widgets drifting out of sync
- Future consumers (spectrum-widget VFO marker? hardware controller LED? status bar?) connect to one signal, not two
- Simpler clean-up: `setSlice()` only needs to drop one connection per widget instead of resetting per-widget state
Why it can wait
The current implementation works correctly. The two timers always start within microseconds of each other and clear within a frame. UX is unified in practice; this is structural polish, not a bug fix.
Effort
~1 hour: refactor SliceModel, drop per-widget timer state in both widgets, run existing manual tests for the LOCKED-feedback paths.
References
Context
PR #2905 added LOCKED visual feedback to the VFO and RX applet frequency displays when a tune attempt is blocked by the slice lock. Each widget currently has its own `m_lockedFrequencyTimer` (single-shot, 500ms). When `SliceModel::tuneBlockedByLock()` fires, both widgets start independent timers and both display "LOCKED" simultaneously.
Visually unified (they clear within a frame of each other), but it's two timers and two state bools tracking the same UX concept.
Proposed refactor
Move the feedback timing and "is feedback active" state to `SliceModel` itself, so a single source of truth drives every consumer.
Sketch
```cpp
// SliceModel.h
signals:
void tuneBlockedByLock(); // existing — kept for one-shot reaction
void lockedFeedbackActive(bool on); // new — drives visual state
private:
QTimer m_lockedFeedbackTimer; // 500ms single-shot
bool m_lockedFeedbackActive{false};
```
The two emit signals:
VfoWidget and RxApplet then:
Why this is worth doing
Why it can wait
The current implementation works correctly. The two timers always start within microseconds of each other and clear within a frame. UX is unified in practice; this is structural polish, not a bug fix.
Effort
~1 hour: refactor SliceModel, drop per-widget timer state in both widgets, run existing manual tests for the LOCKED-feedback paths.
References