Skip to content

Promote dev → staging - #910

Merged
patrickrb merged 12 commits into
stagingfrom
dev
Sep 24, 2026
Merged

patrickrb merged 12 commits into
stagingfrom
dev

Conversation

@patrickrb

Copy link
Copy Markdown
Owner

Promotes the current dev into staging.

Notable changes since last promotion

See the full commit list in the PR's Commits tab.

🤖 Generated with Claude Code

patrickrb and others added 12 commits September 23, 2026 17:22
VOX TX crashed the app (SIGABRT) every time it should have keyed, once a
DigiRig/USB audio interface was attached. Attaching the interface posts an
AVAudioEngineConfigurationChange, which tears down every connection in the
shared AVAudioEngine — including the TX playerNode -> mainMixerNode link.
TxPlayerService cached `isAttached = true` for the app's lifetime and never
reconnected, so at TX time AVAudioPlayerNode.play() ran on a disconnected
node and AVFAudio raised an uncaught NSException -> abort. AudioCaptureService
only rebuilds the capture (input) side on config change, so the TX side was
left dead.

Split the lifecycle into attach (once) vs connect (per graph rebuild) via a
pure `TxConnectionState`, observe AVAudioEngineConfigurationChange in
TxPlayerService, and invalidate the connection + drop the stale converter so
the next play() reconnects and rebuilds the converter against the current
mixer format. Diagnosed from three identical device crash reports
(TxPlayerService.play -> AVAudioPlayerNode play -> StartImpl NSException).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to the config-change reconnect fix, resolving three review findings:

1. Residual crash window (correctness): a config change delivered between
   ensureConnected() and playerNode.play() still tore down the node connection,
   and play() on a disconnected node raises an uncaught Obj-C NSException that
   aborts the app — Swift's `try` can't catch it. Added a minimal Obj-C
   exception-catch shim (ExceptionCatcher + bridging header) and route the
   stop/schedule/play keying through it; on a raised exception we invalidate the
   connection and bail (TX lost for that slot, app survives). Also made
   ensureConnected() return the converter directly so play() no longer re-reads
   outputConverter outside the lock (closes the data race with handleConfigChange).

2. Observer leak (correctness): configure() runs on every LiveEngine start
   (stop->start reuses the same long-lived engine), so a bare addObserver stacked
   a new config-change observer each cycle. removeObserver(self) first to keep it
   idempotent.

3. Dead code (cleanup): dropped TxConnectionState.isReady and its unused
   Equatable conformance (referenced only by tests); tests now assert
   needsAttach/needsConnect — the flags production actually branches on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A pure rename, kept separate so `git log --follow AGENTS.md` and blame keep
the file's history. The next commit makes the wording agent-neutral and
recreates CLAUDE.md as a pointer. Refs #843.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AGENTS.md is now the single source of rules for every contributor, human or
coding agent. CLAUDE.md shrinks to a pointer that imports it (@AGENTS.md), so
Claude Code still loads the full rules. Every rule, command and gotcha is kept;
only the title and the local-notes file name change (AGENTS.local.md, with
CLAUDE.local.md still ignored and still working).

- Testing gains iOS and desktop subsections with the verified commands, and
  notes the CI gaps. swift test needs full Xcode (XCTest).
- Comments in 12 source files and the .gitignore note now point to AGENTS.md.
- README links AGENTS.md from a new Contributing section.
- .github/scripts/check-agents-md.sh (with --self-test) and the agents-md
  workflow fail if AGENTS.md is missing, if CLAUDE.md loses its import, or if
  any file still refers to CLAUDE.md.

Closes #843.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rig meter callback is live whenever PTT is keyed — during an FT8
over and during a user-initiated tune carrier alike. High SWR during a
tune is the expected starting condition (the operator keys a carrier
precisely to bring a match down), not a fault, but the SWR halt was
firing anyway: it set the lockout, showed the banner, and the SWR_LOCKED
gate then blocked the very retune the operator pressed Tune for.

Thread a `tuning` flag through the pure `shouldHaltForSwr` gate so it
returns false while a tune carrier is up, and pass
`transmitSignal.isTuning()` from `onMeterUpdate`. The FT8-over halt path
is unchanged. The tune worker keeps its own bounded max-on timeout for
PA safety, and the diagnostic log now records "tune (halt suppressed)"
so the reason is visible in debug.log.

Adds shouldHalt_neverWhileTuning and updates the two existing callers
for the new signature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix: don't let SWR protection interrupt a user tune
Move agent instructions to tool-neutral AGENTS.md; CLAUDE.md becomes a pointer
Turn the always-open operate control strip into a Material bottom-sheet
drawer, per the "FT8AF app UX redesign" concept (options 3a/3b):

- Collapsed peek (3a): rig/CAT status line docked above a rounded-top
  drawer surface with a drag handle, the primary Call CQ / Stop action,
  and the TX-period toggle. Tap the handle or swipe up to open.
- Expanded sheet (3b): the full control set — Band & Mode, Call CQ
  (+ MORE) / Hunt, TX Period / Tune / DX, and the power slider — over a
  scrim, reusing FT8AFBottomSheet (swipe-down / scrim-tap / Back dismiss).

The control widgets are extracted from TxStrip into shared internal
composables (OperateStatusRow, BandModeRow, CqHuntRow, PeriodTuneDxRow,
TxVolumeRow) so the peek, the expanded sheet, and the legacy strip all
render identical controls and drive one set of hoisted action lambdas.

Shipped behind a "Controls Drawer" setting (Radio & Audio, default ON,
mirrors the showTxVolumeSlider pattern); off restores the original
always-open strip. The drag-to-settle rule (drawerTargetExpanded) is
extracted and unit-tested.

Verified on emulator: peek, expand, collapse, and the settings toggle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously the expanded controls were a separate modal (FT8AFBottomSheet)
that slid up over the collapsed peek and reordered the controls, so it read
as two stacked sheets rather than one drawer.

Rework it into a single sheet that grows in place: one overlay whose bottom
is anchored to a reserved peek slot in the main column and whose height
animates between peek and expanded. The primary Call CQ / Stop + TX-period
row is the top of the sheet in both states; Band & Mode, Hunt, Tune / DX,
and the power slider expand into view below it. The handle drags live
(follows the finger) and taps to toggle; a scrim over the content above
(not the tab bar) fades in and taps to collapse.

Also add a compact TX-period variant ("1st"/"2nd", no caption) so the
segmented control reads cleanly in the narrow slot beside Call CQ, and
tighten the peek height so no secondary row peeks through when collapsed.

Verified on emulator: peek, drag/tap expand, collapse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Move Hunt up beside Call CQ in the always-visible peek (the mockup 3b
  primary pairing), reusing the shared CqHuntRow. TX period drops into the
  expanded section on its own row with Tune/DX (PeriodTuneDxRow) — there
  isn't room for a third control beside the big Call CQ button. Reverts the
  now-unused compact TX-period variant.
- Make it obvious the sheet pulls up: the collapsed handle now shows a
  grabber bar plus an up-chevron and an amber "More controls" caption that
  gently bobs; expanded, it shows a down-chevron to collapse.

Verified on emulator: peek shows Call CQ + Hunt + the hint; expand reveals
Band & Mode, TX Period / Tune / DX, and power.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat: collapsing bottom-sheet controls drawer (design 3a/3b)
fix: reconnect iOS TX player node after an audio config change
@codecov

codecov Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 8.79433% with 643 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.73%. Comparing base (e4c7600) to head (440d4f1).
⚠️ Report is 31 commits behind head on staging.

Files with missing lines Patch % Lines
...kotlin/radio/ks3ckc/ft8af/ui/components/TxStrip.kt 0.00% 191 Missing ⚠️
...app/src/main/kotlin/radio/ks3ckc/ft8af/FT8AFApp.kt 0.00% 157 Missing ⚠️
...s3ckc/ft8af/ui/components/OperateControlsDrawer.kt 4.16% 138 Missing ⚠️
...lin/radio/ks3ckc/ft8af/ui/logbook/LogbookScreen.kt 0.00% 116 Missing ⚠️
...in/kotlin/radio/ks3ckc/ft8af/ui/pota/PotaScreen.kt 12.50% 14 Missing ⚠️
...dio/ks3ckc/ft8af/ui/settings/RadioAudioSettings.kt 0.00% 14 Missing ⚠️
.../main/kotlin/radio/ks3ckc/ft8af/pota/PotaClient.kt 64.28% 10 Missing ⚠️
...tlin/radio/ks3ckc/ft8af/pota/PotaParkRepository.kt 0.00% 1 Missing and 1 partial ⚠️
...lin/radio/ks3ckc/ft8af/ui/pota/PotaAdifExporter.kt 83.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##             staging     #910      +/-   ##
=============================================
- Coverage      42.09%   41.73%   -0.37%     
  Complexity       252      252              
=============================================
  Files            277      279       +2     
  Lines          33233    33668     +435     
  Branches        3887     3958      +71     
=============================================
+ Hits           13991    14052      +61     
- Misses         18957    19329     +372     
- Partials         285      287       +2     
Flag Coverage Δ
android 17.62% <8.79%> (-0.09%) ⬇️
desktop 63.95% <ø> (ø)
ios 97.71% <ø> (ø)
native 9.93% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
desktop/src-tauri/src/engine.rs 31.72% <ø> (ø)
...kotlin/radio/ks3ckc/ft8af/ui/logbook/AdifImport.kt 100.00% <100.00%> (ø)
ios/FT8AFKit/Sources/FT8Engine/TxTiming.swift 100.00% <ø> (ø)
.../FT8AFKit/Tests/FT8EngineTests/TxTimingTests.swift 100.00% <ø> (ø)
...lin/radio/ks3ckc/ft8af/ui/pota/PotaAdifExporter.kt 69.35% <83.33%> (+0.18%) ⬆️
...tlin/radio/ks3ckc/ft8af/pota/PotaParkRepository.kt 35.93% <0.00%> (-1.16%) ⬇️
.../main/kotlin/radio/ks3ckc/ft8af/pota/PotaClient.kt 17.60% <64.28%> (+6.36%) ⬆️
...in/kotlin/radio/ks3ckc/ft8af/ui/pota/PotaScreen.kt 7.01% <12.50%> (+0.29%) ⬆️
...dio/ks3ckc/ft8af/ui/settings/RadioAudioSettings.kt 0.26% <0.00%> (-0.01%) ⬇️
...lin/radio/ks3ckc/ft8af/ui/logbook/LogbookScreen.kt 6.78% <0.00%> (-0.60%) ⬇️
... and 3 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@patrickrb
patrickrb merged commit 353723f into staging Sep 24, 2026
42 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.

2 participants