Skip to content

Improve virtual FlexControl wheel tuning#3029

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
rfoust:codex/flexcontrol-wheel-sensitivity
May 24, 2026
Merged

Improve virtual FlexControl wheel tuning#3029
ten9876 merged 1 commit into
aethersdr:mainfrom
rfoust:codex/flexcontrol-wheel-sensitivity

Conversation

@rfoust

@rfoust rfoust commented May 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a Mouse Sensitivity slider for captured virtual FlexControl wheel movement
  • reorganize the virtual wheel controls with centered Wheel Tightness and Mouse Sensitivity labels plus Tight/Loose and Less/More endpoint labels
  • reduce jumpy mouse tuning by clamping large single-event pointer deltas, lazily re-anchoring after near-center movement, and separating pointer timing from coast timing
  • prevent slow mouse movement from starting free coasting and clarify the tight/loose tooltip is primarily intended for trackpads

Validation

  • git diff --check
  • cmake --build build --parallel

Copilot AI review requested due to automatic review settings May 24, 2026 02:51
@rfoust rfoust requested review from jensenpat and ten9876 as code owners May 24, 2026 02:51

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

Enhances the virtual FlexControl wheel’s captured pointer tuning behavior and UI controls by adding a sensitivity setting and refining the capture/coast mechanics to reduce jumpiness and unintended coasting.

Changes:

  • Added a persisted “Mouse Sensitivity” slider and applied sensitivity scaling to captured wheel rotation.
  • Reduced jumpy tuning by clamping per-event pointer deltas, adding anchor logic near center, and separating pointer timing from coast timing.
  • Reorganized the control strip layout and updated labels/tooltips for wheel tightness and sensitivity.

Reviewed changes

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

File Description
src/gui/FlexControlDialog.h Adds member storage for the new sensitivity slider.
src/gui/FlexControlDialog.cpp Implements sensitivity persistence + tuning behavior changes and updates the control strip UI layout/tooltips.

Comment thread src/gui/FlexControlDialog.cpp Outdated
Comment on lines +1107 to +1111
AppSettings::instance().value("FlexControlVirtualWheelLooseness", "45").toInt(),
0, 100);
m_wheel->setLooseness(looseness);
const int sensitivity = loadVirtualWheelSensitivity();
m_wheel->setSensitivity(sensitivity);

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.

Addressed in f09e8695: virtual wheel looseness and sensitivity are now stored together under FlexControlVirtualWheel, with a migration path that reads the legacy FlexControlVirtualWheelLooseness value, writes it into the JSON object, removes the legacy key, and saves the migrated settings.

@rfoust rfoust self-assigned this May 24, 2026
@rfoust rfoust force-pushed the codex/flexcontrol-wheel-sensitivity branch from ddad460 to f09e869 Compare May 24, 2026 03:00
@rfoust rfoust force-pushed the codex/flexcontrol-wheel-sensitivity branch from f09e869 to 13d7940 Compare May 24, 2026 03:17

@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 @rfoust — nice quality-of-life improvement. The wheel feels much better with the pointer-delta clamp + lazy re-anchoring, and decoupling m_pointerClock from m_spinClock is the right fix for the coast-restart problem.

A few small observations, nothing blocking:

Settings migration (good — Copilot concern resolved)

The consolidated FlexControlVirtualWheel JSON object with legacy FlexControlVirtualWheelLooseness migration matches the pattern used in src/models/AntennaAliasStore.cpp (raw → QJsonDocument::fromJsontoUtf8(), save via QJsonDocument(obj).toJson(Compact)). Looks consistent with project conventions.

Minor: load/save churn on slider drag

Every QSlider::valueChanged tick calls saveVirtualWheel{Looseness,Sensitivity}, which in turn re-parses the JSON, edits one field, re-serializes, and calls AppSettings::save(). During a slider drag this can be hundreds of parse/serialize/save cycles per second. The values are tiny so it's probably fine in practice, but you could either:

  • coalesce writes (e.g. via QTimer::singleShot(0, ...) or save only on sliderReleased), or
  • cache the parsed QJsonObject in the helper.

Up to you whether it's worth bothering with for two ints.

Minor: loadVirtualWheelSettings() called twice during construction

loadVirtualWheelLooseness() and loadVirtualWheelSensitivity() each re-parse the JSON (and each one re-runs the legacy-key migration check). Functionally correct, but trivially you could read both fields in one pass during the constructor. Again, micro-scale.

Tooltip consistency

The Wheel Tightness tooltip now mentions "Primarily intended for trackpads", but the new Mouse Sensitivity tooltip doesn't. If the trackpad guidance applies to both controls, worth mirroring; if not, ignore.

Scope / correctness

  • Files limited to FlexControlDialog.{cpp,h} — matches stated scope. ✓
  • AppSettings (not QSettings) used throughout. ✓
  • m_wheel is null-guarded in both slider lambdas. ✓
  • m_hasPointerAnchor is reset in releaseCapture(). ✓
  • sensitivityScale() at m_sensitivity = 50 returns 1.0, preserving the prior behavior as the midpoint. ✓

LGTM otherwise.


🤖 aethersdr-agent · cost: $4.2299 · model: claude-opus-4-7

@ten9876 ten9876 merged commit 0a98432 into aethersdr:main May 24, 2026
5 checks passed
aethersdr-agent Bot added a commit that referenced this pull request May 24, 2026
… comments (#3047). Principle XI.

PR #3034 introduced two comment blocks in src/gui/MainWindow.cpp tagged
(#3029). That number refers to an unrelated FlexControl wheel-tuning PR
that merged the same night; the meaningful reference for the panadapter
dBm-range prime fix is #3034 itself. Correct both citations so code
archaeology lands on the actual fix PR.

Comment-only change; no behavior, no symbols touched.
ten9876 added a commit that referenced this pull request May 24, 2026
Elevates two community contributors to Tier 3 owner status across
the architecture & infrastructure paths:

  - @NF0T (Ryan) — shipped Windows-installer RADE integration; deep
    Windows-build expertise and FreeDV / digital-voice protocol
    background. Distinct from @tmiw (Mooneer, FreeDV project lead).
  - @rfoust (Robbie Foust) — shipped the FlexControl wheel tuning
    overhaul (#3029, merged today) with thorough adherence to project
    conventions; demonstrated solid grasp of the input/UX layer.

Tier 3 paths covered (no change to which paths are at Tier 3):

  - src/gui/MainWindow.{cpp,h}              (visual/UX direction)
  - src/models/RadioModel.{cpp,h}           (central state, threading)
  - src/core/AudioEngine.{cpp,h}            (audio pipeline)
  - src/core/PanadapterStream.{cpp,h}       (VITA-49 protocol bedrock)
  - CMakeLists.txt                          (build configuration)
  - .github/codeql/                         (security tooling)
  - .claude/commands/                       (agent configuration)

Tier 3 means: can approve PRs touching these paths; the bot
(@AetherClaude) explicitly cannot. Tier 4 (maintainer-only) remains
@ten9876-only — Ryan and Robbie do NOT gain approval rights on
project-policy / repo-canon files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ten9876 added a commit that referenced this pull request May 24, 2026
… T1, infra in T2 (#3053)

## Summary

Final CODEOWNERS structure aligned to the maintainer-direction
three-tier framework, with progressively narrower owner sets at higher
tiers. All approvals human-only; @AetherClaude absent from every owner
set.

Final state in commit \`1aa9a446\` (8 commits total on the branch
documenting the iterative refinement).

## Final tier structure

| Tier | Owners | Coverage |
|---|---|---|
| **Tier 1** | @ten9876 | Governance/direction, security, bot policy, CI
workflows |
| **Tier 2** | @ten9876 + @jensenpat | Project infrastructure (docs,
tests, GH tooling, build config) |
| **Tier 3** | @ten9876 + @jensenpat + @NF0T + @rfoust | Source code
(broad default) |

Each tier has a progressively wider roster:
- **T1 is single-approver** for governance/security/bot policy where
direction needs to be authoritative.
- **T2 narrows the source-review pool to 2** because infrastructure
(docs, tests, CI, build config) needs deeper repo context than routine
source review.
- **T3 has the full 4-human roster** because routine source review
benefits from more eyes.

## Detailed path coverage

**Tier 1 — @ten9876:**
- Governance/direction: \`AGENTS.md\`, \`CLAUDE.md\`, \`GEMINI.md\`,
\`CONSTITUTION.md\`, \`GOVERNANCE.md\`, \`CONTRIBUTING.md\`,
\`CODE_OF_CONDUCT.md\`, \`LICENSE\`, \`ROADMAP.md\`
- Security/compliance: \`SECURITY.md\`, \`SECURITY-AUDIT.md\`,
\`THIRD_PARTY_LICENSES\`, \`.github/CODEOWNERS\`, \`.github/codeql/\`,
\`docs/RELEASE-SIGNING-KEY.pub.asc\`
- CI runtime (kept here despite the framework's CI-in-T2 default —
workflow changes can exfiltrate secrets or publish unsigned releases):
\`.github/workflows/\`
- Bot/agent instruction: \`.claude/commands/\`

**Tier 2 — @ten9876 + @jensenpat:**
- Documentation & tests: \`tests/\`, \`docs/\`, \`*.md\` (catches
README.md, CHANGELOG.md, SUPPORT.md by fall-through)
- GitHub-specific tooling: \`.github/dependabot.yml\`,
\`.github/docker/\`, \`.github/ISSUE_TEMPLATE/\`
- Build configuration: \`CMakeLists.txt\`

**Tier 3 — @ten9876 + @jensenpat + @NF0T + @rfoust:**
- \`*\` catches source and anything not enumerated: \`src/\`,
\`third_party/\`, \`plugins/\`, \`hal-plugin/\`, \`resources/\`,
\`packaging/\`, \`scripts/\`

## How last-match-wins resolves (post-merge)

| File | Pattern that matches | Tier | Owner set |
|---|---|---|---|
| \`CLAUDE.md\` | T1: \`CLAUDE.md\` (overrides \`*.md\`) | T1 | @ten9876
|
| \`README.md\` | T2: \`*.md\` (no T1 override) | T2 | @ten9876 +
@jensenpat |
| \`CHANGELOG.md\` | T2: \`*.md\` | T2 | @ten9876 + @jensenpat |
| \`docs/install.md\` | T2: \`docs/\` (last match) | T2 | @ten9876 +
@jensenpat |
| \`.github/workflows/ci.yml\` | T1: \`.github/workflows/\` | T1 |
@ten9876 |
| \`.github/codeql/codeql-config.yml\` | T1: \`.github/codeql/\` | T1 |
@ten9876 |
| \`.claude/commands/loop.md\` | T1: \`.claude/commands/\` (overrides
\`*.md\`) | T1 | @ten9876 |
| \`src/gui/MainWindow.cpp\` | T3: \`*\` | T3 | 4 humans |
| \`tests/foo.cpp\` | T2: \`tests/\` | T2 | @ten9876 + @jensenpat |
| \`CMakeLists.txt\` | T2: \`CMakeLists.txt\` | T2 | @ten9876 +
@jensenpat |

## Co-maintainer roster

- **@NF0T (Ryan)** — Windows-installer RADE integration; Windows-build +
FreeDV protocol background. Source-code Tier 3 only.
- **@rfoust (Robbie Foust)** — FlexControl wheel tuning overhaul
([#3029](#3029)); input/UX
layer expertise. Source-code Tier 3 only.

## Stats

- 1 file change to \`.github/CODEOWNERS\`
- 8 commits total on the branch documenting the iterative refinement

## Test plan

- [x] Diff review confirms only \`.github/CODEOWNERS\` is touched
- [x] \`grep -c AetherClaude .github/CODEOWNERS\` returns 1 (only the
documentary inline mention)
- [x] \`grep "@NF0T" .github/CODEOWNERS\` returns 2 matches (header doc
+ Tier 3 \`*\` line only)
- [ ] GitHub validates CODEOWNERS syntax on push (red banner on PR if
invalid)
- [ ] After merge: PR touching \`CLAUDE.md\` requires \`@ten9876\` (T1)
- [ ] After merge: PR touching \`README.md\` is approvable by @ten9876
or @jensenpat (T2 — narrower than before)
- [ ] After merge: PR touching \`docs/foo.md\` requires @ten9876 or
@jensenpat (T2)
- [ ] After merge: PR touching \`.github/workflows/ci.yml\` requires
\`@ten9876\` (T1)
- [ ] After merge: PR touching \`src/gui/MainWindow.cpp\` approvable by
any of 4 humans (T3)
- [ ] After merge: bot-authored PRs cannot self-approve via CODEOWNERS
regardless of path

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ten9876 pushed a commit that referenced this pull request May 24, 2026
… comments (#3047). Principle XI. (#3048)

## Summary

Fixes #3047

### What was changed

cleanup(panadapter): fix stale #3029 references to #3034 in dBm-prime
comments (#3047). Principle XI.

### Files modified

- `src/gui/MainWindow.cpp`

```
 src/gui/MainWindow.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
```

---
Generated by AetherClaude (automated agent for AetherSDR)

---
<sub>🤖 aethersdr-agent · cost: $1.7186 · model: claude-opus-4-7</sub>

Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com>
ten9876 added a commit that referenced this pull request May 25, 2026
…ESC asymmetry) (#3103)

## Summary

Changes AetherControl's virtual FlexControl knob from a click-to-latch /
Escape-to-unlatch UX to a **double-click for both directions**, with
Escape kept as a secondary safety release.

## Why

Operators report losing track of the release path. First-time users miss
the small "Press ESC to release" hint at the bottom of the dialog and
feel stuck with the cursor captured.

## What changed

| Action | Before | After |
|---|---|---|
| **Latch** | Single left-click on wheel | **Double-click** on wheel |
| **Unlatch** | ESC key | **Double-click** on wheel (primary) or ESC
(secondary safety) |
| Single click on wheel | Latched | No-op (event accepted to suppress
accidental window drag) |

Updated user-visible strings:
- Resting hint: `"Double-click the knob to capture circular tuning."`
- Armed hint: `"Mouse locked to FlexControl. Double-click the knob to
release (or press ESC)."`
- Accessible description mirrors the new gesture.

ESC stays as a backup for the edge case where the cursor ended up
offscreen and can't get back over the knob to double-click.

## Attribution

AetherControl original implementation by
[@rfoust](https://github.com/rfoust)
([#2888](#2888)); FlexControl
wheel UX overhaul also by @rfoust
([#3029](#3029)). This change
preserves the existing wheel-feel work and only touches the
capture/release gesture.

## Test plan

- [x] Linux build clean
- [ ] CI: build / check-paths / check-windows / CodeQL
- [ ] Manual: open AetherControl (Settings → FlexControl), single-click
on wheel does nothing, double-click captures, double-click again
releases, ESC still releases as backup

73, Jeremy KK7GWY & Claude (AI dev partner)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
## Summary
- add a Mouse Sensitivity slider for captured virtual FlexControl wheel
movement
- reorganize the virtual wheel controls with centered Wheel Tightness
and Mouse Sensitivity labels plus Tight/Loose and Less/More endpoint
labels
- reduce jumpy mouse tuning by clamping large single-event pointer
deltas, lazily re-anchoring after near-center movement, and separating
pointer timing from coast timing
- prevent slow mouse movement from starting free coasting and clarify
the tight/loose tooltip is primarily intended for trackpads

## Validation
- git diff --check
- cmake --build build --parallel
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
… T1, infra in T2 (aethersdr#3053)

## Summary

Final CODEOWNERS structure aligned to the maintainer-direction
three-tier framework, with progressively narrower owner sets at higher
tiers. All approvals human-only; @AetherClaude absent from every owner
set.

Final state in commit \`1aa9a446\` (8 commits total on the branch
documenting the iterative refinement).

## Final tier structure

| Tier | Owners | Coverage |
|---|---|---|
| **Tier 1** | @ten9876 | Governance/direction, security, bot policy, CI
workflows |
| **Tier 2** | @ten9876 + @jensenpat | Project infrastructure (docs,
tests, GH tooling, build config) |
| **Tier 3** | @ten9876 + @jensenpat + @NF0T + @rfoust | Source code
(broad default) |

Each tier has a progressively wider roster:
- **T1 is single-approver** for governance/security/bot policy where
direction needs to be authoritative.
- **T2 narrows the source-review pool to 2** because infrastructure
(docs, tests, CI, build config) needs deeper repo context than routine
source review.
- **T3 has the full 4-human roster** because routine source review
benefits from more eyes.

## Detailed path coverage

**Tier 1 — @ten9876:**
- Governance/direction: \`AGENTS.md\`, \`CLAUDE.md\`, \`GEMINI.md\`,
\`CONSTITUTION.md\`, \`GOVERNANCE.md\`, \`CONTRIBUTING.md\`,
\`CODE_OF_CONDUCT.md\`, \`LICENSE\`, \`ROADMAP.md\`
- Security/compliance: \`SECURITY.md\`, \`SECURITY-AUDIT.md\`,
\`THIRD_PARTY_LICENSES\`, \`.github/CODEOWNERS\`, \`.github/codeql/\`,
\`docs/RELEASE-SIGNING-KEY.pub.asc\`
- CI runtime (kept here despite the framework's CI-in-T2 default —
workflow changes can exfiltrate secrets or publish unsigned releases):
\`.github/workflows/\`
- Bot/agent instruction: \`.claude/commands/\`

**Tier 2 — @ten9876 + @jensenpat:**
- Documentation & tests: \`tests/\`, \`docs/\`, \`*.md\` (catches
README.md, CHANGELOG.md, SUPPORT.md by fall-through)
- GitHub-specific tooling: \`.github/dependabot.yml\`,
\`.github/docker/\`, \`.github/ISSUE_TEMPLATE/\`
- Build configuration: \`CMakeLists.txt\`

**Tier 3 — @ten9876 + @jensenpat + @NF0T + @rfoust:**
- \`*\` catches source and anything not enumerated: \`src/\`,
\`third_party/\`, \`plugins/\`, \`hal-plugin/\`, \`resources/\`,
\`packaging/\`, \`scripts/\`

## How last-match-wins resolves (post-merge)

| File | Pattern that matches | Tier | Owner set |
|---|---|---|---|
| \`CLAUDE.md\` | T1: \`CLAUDE.md\` (overrides \`*.md\`) | T1 | @ten9876
|
| \`README.md\` | T2: \`*.md\` (no T1 override) | T2 | @ten9876 +
@jensenpat |
| \`CHANGELOG.md\` | T2: \`*.md\` | T2 | @ten9876 + @jensenpat |
| \`docs/install.md\` | T2: \`docs/\` (last match) | T2 | @ten9876 +
@jensenpat |
| \`.github/workflows/ci.yml\` | T1: \`.github/workflows/\` | T1 |
@ten9876 |
| \`.github/codeql/codeql-config.yml\` | T1: \`.github/codeql/\` | T1 |
@ten9876 |
| \`.claude/commands/loop.md\` | T1: \`.claude/commands/\` (overrides
\`*.md\`) | T1 | @ten9876 |
| \`src/gui/MainWindow.cpp\` | T3: \`*\` | T3 | 4 humans |
| \`tests/foo.cpp\` | T2: \`tests/\` | T2 | @ten9876 + @jensenpat |
| \`CMakeLists.txt\` | T2: \`CMakeLists.txt\` | T2 | @ten9876 +
@jensenpat |

## Co-maintainer roster

- **@NF0T (Ryan)** — Windows-installer RADE integration; Windows-build +
FreeDV protocol background. Source-code Tier 3 only.
- **@rfoust (Robbie Foust)** — FlexControl wheel tuning overhaul
([aethersdr#3029](aethersdr#3029)); input/UX
layer expertise. Source-code Tier 3 only.

## Stats

- 1 file change to \`.github/CODEOWNERS\`
- 8 commits total on the branch documenting the iterative refinement

## Test plan

- [x] Diff review confirms only \`.github/CODEOWNERS\` is touched
- [x] \`grep -c AetherClaude .github/CODEOWNERS\` returns 1 (only the
documentary inline mention)
- [x] \`grep "@NF0T" .github/CODEOWNERS\` returns 2 matches (header doc
+ Tier 3 \`*\` line only)
- [ ] GitHub validates CODEOWNERS syntax on push (red banner on PR if
invalid)
- [ ] After merge: PR touching \`CLAUDE.md\` requires \`@ten9876\` (T1)
- [ ] After merge: PR touching \`README.md\` is approvable by @ten9876
or @jensenpat (T2 — narrower than before)
- [ ] After merge: PR touching \`docs/foo.md\` requires @ten9876 or
@jensenpat (T2)
- [ ] After merge: PR touching \`.github/workflows/ci.yml\` requires
\`@ten9876\` (T1)
- [ ] After merge: PR touching \`src/gui/MainWindow.cpp\` approvable by
any of 4 humans (T3)
- [ ] After merge: bot-authored PRs cannot self-approve via CODEOWNERS
regardless of path

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…#3034 in dBm-prime comments (aethersdr#3047). Principle XI. (aethersdr#3048)

## Summary

Fixes aethersdr#3047

### What was changed

cleanup(panadapter): fix stale aethersdr#3029 references to aethersdr#3034 in dBm-prime
comments (aethersdr#3047). Principle XI.

### Files modified

- `src/gui/MainWindow.cpp`

```
 src/gui/MainWindow.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
```

---
Generated by AetherClaude (automated agent for AetherSDR)

---
<sub>🤖 aethersdr-agent · cost: $1.7186 · model: claude-opus-4-7</sub>

Co-authored-by: aethersdr-agent[bot] <273844287+aethersdr-agent[bot]@users.noreply.github.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…ESC asymmetry) (aethersdr#3103)

## Summary

Changes AetherControl's virtual FlexControl knob from a click-to-latch /
Escape-to-unlatch UX to a **double-click for both directions**, with
Escape kept as a secondary safety release.

## Why

Operators report losing track of the release path. First-time users miss
the small "Press ESC to release" hint at the bottom of the dialog and
feel stuck with the cursor captured.

## What changed

| Action | Before | After |
|---|---|---|
| **Latch** | Single left-click on wheel | **Double-click** on wheel |
| **Unlatch** | ESC key | **Double-click** on wheel (primary) or ESC
(secondary safety) |
| Single click on wheel | Latched | No-op (event accepted to suppress
accidental window drag) |

Updated user-visible strings:
- Resting hint: `"Double-click the knob to capture circular tuning."`
- Armed hint: `"Mouse locked to FlexControl. Double-click the knob to
release (or press ESC)."`
- Accessible description mirrors the new gesture.

ESC stays as a backup for the edge case where the cursor ended up
offscreen and can't get back over the knob to double-click.

## Attribution

AetherControl original implementation by
[@rfoust](https://github.com/rfoust)
([aethersdr#2888](aethersdr#2888)); FlexControl
wheel UX overhaul also by @rfoust
([aethersdr#3029](aethersdr#3029)). This change
preserves the existing wheel-feel work and only touches the
capture/release gesture.

## Test plan

- [x] Linux build clean
- [ ] CI: build / check-paths / check-windows / CodeQL
- [ ] Manual: open AetherControl (Settings → FlexControl), single-click
on wheel does nothing, double-click captures, double-click again
releases, ESC still releases as backup

73, Jeremy KK7GWY & Claude (AI dev partner)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (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.

3 participants