Watch: show version/build on screen, surface recording errors in UI

A tester on real hardware has no console, so a failed start looked like
a dead button and there was no way to tell which build the watch had
actually installed. Show the stamped version/build under the record
button, display the last start error in place of the hint text, and
fall back to the default audio session mode if .spokenAudio is rejected
with the .record category.

Claude-Session: https://claude.ai/code/session_01GKfAiKiQKLDTmbiGva4FGE
This commit is contained in:
2026-07-15 16:07:32 -04:00
parent 8bdda913a7
commit 798abf3b90
3 changed files with 31 additions and 1 deletions
@@ -71,7 +71,13 @@ final class WatchAudioRecorder {
guard !isRecording else { return }
let session = AVAudioSession.sharedInstance()
try session.setCategory(.record, mode: .spokenAudio)
do {
try session.setCategory(.record, mode: .spokenAudio)
} catch {
// .spokenAudio isn't guaranteed compatible with .record on every
// route fall back rather than refuse to record at all.
try session.setCategory(.record, mode: .default)
}
try session.setActive(true)
let dir = Self.recordingsDirectory
+17
View File
@@ -17,6 +17,12 @@ struct RecordView: View {
Text(Self.elapsedString(recorder.elapsed))
.font(.system(.title3, design: .rounded).monospacedDigit())
.foregroundStyle(.secondary)
} else if let error = services.lastError {
Text(error)
.font(.caption2)
.foregroundStyle(.red)
.multilineTextAlignment(.center)
.lineLimit(3)
} else {
Text("Tap to record")
.font(.footnote)
@@ -24,6 +30,7 @@ struct RecordView: View {
}
pendingFooter
}
buildFooter
}
.padding()
.containerBackground(.red.gradient, for: .navigation)
@@ -76,6 +83,16 @@ struct RecordView: View {
}
}
/// Version + build stamped by update_build_number.sh, so a tester can tell
/// at a glance which build the watch actually installed.
private var buildFooter: some View {
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "?"
let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "?"
return Text("v\(version) (\(build))")
.font(.system(size: 10))
.foregroundStyle(.tertiary)
}
/// mm:ss for the elapsed readout.
static func elapsedString(_ interval: TimeInterval) -> String {
let total = Int(interval)
+7
View File
@@ -34,6 +34,11 @@ final class WatchAppServices {
private let logger = Logger(subsystem: "dev.rzen.indie.Notes.watchkitapp", category: "recording")
/// Why the last start attempt failed, surfaced in the UI on a real watch
/// there's no console, so a silent failure is indistinguishable from a dead
/// button. Cleared when a recording starts successfully.
private(set) var lastError: String?
/// Request permission (once) then begin recording. A no-op if already
/// recording or permission is refused.
func startRecording() async {
@@ -44,8 +49,10 @@ final class WatchAppServices {
}
do {
try recorder.startRecording()
lastError = nil
} catch {
logger.error("could not start recording: \(error, privacy: .public)")
lastError = error.localizedDescription
}
}