Skip to content

[codex] prepare 1.14.4 canonical proxy release#38

Merged
rmarinsky merged 4 commits into
mainfrom
codex/app-1.14.3-canonical-proxy
Jun 1, 2026
Merged

[codex] prepare 1.14.4 canonical proxy release#38
rmarinsky merged 4 commits into
mainfrom
codex/app-1.14.3-canonical-proxy

Conversation

@rmarinsky

@rmarinsky rmarinsky commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Що змінилось

  • Оновлено production proxy URL назад на canonical https://diduny-ears-proxy.fly.dev.
  • Додано migration для користувачів, у яких збережений тимчасовий diduny-ears-proxy-v2.fly.dev.
  • Підготовлено app release placeholder 1.14.4 / build 4; фактична release version береться з GitHub tag.
  • Опубліковано весь поточний worktree release-гілки: permission fixes, in-progress recording persistence, sleep flush/chunk stitcher changes і тести.

Чому

Клієнти мають повернутися з тимчасового v2 endpoint на canonical backend, щоб backend реліз міг відновити стандартний diduny-ears-proxy без ламання existing installs.

Validation

  • xcodebuild -project Diduny.xcodeproj -scheme Diduny -configuration Debug -destination platform=macOS build

Release

  • Label: release:patch
  • Latest GitHub release is v1.14.3; this PR will release v1.14.4.

@rmarinsky rmarinsky added the release:patch Bug fix / internal — bumps patch version label Jun 1, 2026
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🔖 On merge this PR will release v1.14.4 (release:patch).

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rmarinsky, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 28 minutes and 19 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32f3deb8-9ac0-48a7-86cf-0eb4bf4c9ac8

📥 Commits

Reviewing files that changed from the base of the PR and between d7e1645 and 4a42ed3.

📒 Files selected for processing (14)
  • Diduny.xcodeproj/project.pbxproj
  • Diduny/App/AppDelegate+MeetingRecording.swift
  • Diduny/App/AppDelegate+MeetingTranslationRecording.swift
  • Diduny/App/AppDelegate.swift
  • Diduny/Core/Services/MeetingChunkStitcher.swift
  • Diduny/Core/Services/MeetingRecorderService.swift
  • Diduny/Core/Services/PushToTalkService.swift
  • Diduny/Core/Services/SystemAudioCaptureService.swift
  • Diduny/Core/Storage/InProgressRecordingStore.swift
  • DidunyTests/InProgressRecordingStoreTests.swift
  • DidunyTests/MeetingChunkStitcherTests.swift
  • DidunyTests/PushToTalkServiceTests.swift
  • DidunyTests/RecordingModelMigrationTests.swift
  • project.yml
📝 Walkthrough

Walkthrough

This PR implements synchronous sleep handling (RLR-M2) for meeting recordings plus a configurable hold-start delay for push-to-talk dictation. The recording recovery system includes per-recording in-progress directories, time-rotated chunk files with manifest tracking, and chunk stitching on stop. Sleep handlers flush and close active chunks before system sleep completes. Hold-start delay allows users to configure a threshold before recording begins when holding a modifier key.

Changes

Recording Recovery & Sleep Handling

Layer / File(s) Summary
Recovery data models & metadata
Diduny/Core/Models/Recording.swift, Diduny/Core/Storage/InProgressRecordingManifest.swift, DidunyTests/RecordingModelMigrationTests.swift
New RecoverySource enum and Recording.recoverySource property track recovery origin; new partiallyRecovered processing status; on-disk InProgressRecordingManifest schema with chunk entries, audio config, and sleep-interruption flag; backward-compatibility tests ensure legacy recordings decode without recoverySource and new fields round-trip correctly.
In-progress recording storage layer
Diduny/Core/Storage/InProgressRecordingStore.swift, Diduny/Core/Storage/RecordingsLibraryStorage.swift, DidunyTests/InProgressRecordingStoreTests.swift
New InProgressRecordingStore actor manages per-recording subdirectories under Application Support, generates chunk URLs, persists manifests atomically with fsync, and cleans up directories after handoff; RecordingsLibraryStorage preserves recoverySource during audio file replacement; tests validate directory creation, atomic writes, round-trip manifest encoding, missing-file nil returns, and cleanup.
Meeting chunk stitching service
Diduny/Core/Services/MeetingChunkStitcher.swift, DidunyTests/MeetingChunkStitcherTests.swift
New MeetingChunkStitcher combines WAV chunks into single output with duration/sample-rate validation, fast-path for single chunks, and reporting of unreadable/skipped indices; tests cover empty input rejection, single-chunk copy, multi-chunk assembly with duration sums, format-mismatch skipping, and all-unreadable failure.
Audio capture chunk rotation infrastructure
Diduny/Core/Services/SystemAudioCaptureService.swift
Adds chunk rotation state (currentChunkIndex, currentChunkStartedAt, currentChunkFrameCount) and configuration hooks (chunkDurationSeconds, chunkURLProvider, onChunkRotated); implements rotateChunkIfNeededOnFileWriteQueue to close/open chunks at duration boundaries; adds synchronousFlushForSleep() for power-management teardown that marks isCapturing = false and returns closed chunk URL; writeSamples accepts allowRotation flag (disabled during final flush).
Meeting recorder service chunk integration
Diduny/Core/Services/MeetingRecorderService.swift
Allocates per-recording directories via InProgressRecordingStore, writes initial manifest at start with audio config, wires chunk rotation callbacks, stitches chunks into stitched.wav on stop (with last-chunk fallback), and updates manifest with final chunk byte counts/durations/close times; cancelRecording removes entire in-progress directory; chunk-rotation handler updates manifest asynchronously.
Sleep/wake notification coordination
Diduny/Core/Services/SleepFlushCoordinator.swift, DidunyTests/SleepFlushCoordinatorTests.swift
New SleepFlushCoordinator observes NSWorkspace.willSleepNotification and didWakeNotification with synchronous delivery, invokes flushCurrentChunk closure before notification returns (preserving sleep sync guarantee), and triggers onWake on wake thread; tests verify synchronous execution, return value forwarding, nil-safety, and observer cleanup on deallocation.
AppDelegate sleep & wake handlers
Diduny/App/AppDelegate.swift
Registers SleepFlushCoordinator during post-onboarding setup; flushActiveRecordingForSleep() synchronously flushes meeting recorder and marks sleep-interruption flag; asynchronously updates manifest with final chunk metadata and releases App Nap activity tokens; handleWakeAfterRecordingInterrupt() shows notch message and resets recording state if sleep interrupted active recording; applicationWillTerminate clears coordinator and ends activity tokens.
In-progress directory cleanup on recording stop
Diduny/App/AppDelegate+MeetingRecording.swift, Diduny/App/AppDelegate+MeetingTranslationRecording.swift
Recording stop/cancel paths now capture in-progress recording IDs before clearing them, then asynchronously call InProgressRecordingStore.cleanup() on success, cancellation, and error paths to remove the entire recording directory; applied to both voice and translation recording flows.

Configurable Hold-Start Delay for Push-To-Talk

Layer / File(s) Summary
Push-to-talk hold delay state & sanitization
Diduny/Core/Protocols/ServiceProtocols.swift, Diduny/Core/Services/PushToTalkService.swift, DidunyTests/PushToTalkServiceTests.swift
PushToTalkServiceProtocol gains holdStartDelaySeconds property; PushToTalkService sanitizes delays to 0.2–1.0s (0.1-increment rounding), schedules recording start via async Task after delay expires, cancels pending task on key release or mode reset, and prevents stopping before threshold; tests validate no-start on short holds, single start/stop after threshold, hands-free toggle path, and settings clamping.
Hold-start delay settings persistence
Diduny/Core/Storage/SettingsStorage.swift
Adds pushToTalkHoldStartDelaySeconds and translationPushToTalkHoldStartDelaySeconds properties with UserDefaults backing, defaultHoldStartDelaySeconds (0.2s), and sanitizedHoldStartDelaySeconds helper; migrates legacy v2 proxy URL to canonical domain; updates default proxy constant.
UI controls & notification-based hotkey wiring
Diduny/Features/Settings/ShortcutsSettingsView.swift, Diduny/App/AppDelegate+Hotkeys.swift, Diduny/App/AppDelegate.swift
ShortcutsSettingsView adds hold-delay slider (shown when hands-free disabled) wired to SettingsStorage with live save and delay-change notifications; AppDelegate+Hotkeys adds pushToTalkHoldStartDelayChanged and translationPushToTalkHoldStartDelayChanged handlers to update services and reset hands-free mode; AppDelegate listens for new notifications at launch.

Test Coverage & Build Configuration

Layer / File(s) Summary
Project configuration & test wiring
Diduny.xcodeproj/project.pbxproj, Diduny.xcodeproj/xcshareddata/xcschemes/Diduny TEST.xcscheme, project.yml, Diduny/Core/Services/AuthService.swift
Build files add new Swift sources (InProgressRecordingManifest, InProgressRecordingStore, MeetingChunkStitcher, SleepFlushCoordinator and five new test files) to app/test targets; test scheme targets DidunyTests.xctest bundle with parallel test execution; version bumped to 1.14.3 (build 4); AuthService.getAccessToken() now returns token value instead of awaiting only.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • rmarinsky/Diduny#17: Prior major refactor of SystemAudioCaptureService that established the foundation for chunk-based recording.
  • rmarinsky/Diduny#21: Concurrent changes to SystemAudioCaptureService flush behavior and real-time emission ordering.

🐰 Hop, hop, hurrah! A slumbering system shall no longer lose its precious recordings—chunks are stitched, manifests persist, and meetings resume anew when darkness breaks. With configurable delays for hurried thumbs, the app now respects the rhythm of both sleep and speech. What a splendid recovery!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ⚠️ Warning The PR title claims version 1.14.4 but all version bumps in the changeset are to 1.14.3 (MARKETING_VERSION and CURRENT_PROJECT_VERSION), creating a mismatch with the stated objective. Update the PR title to '[codex] prepare 1.14.3 canonical proxy release' to align with the actual version changes in the code.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/app-1.14.3-canonical-proxy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rmarinsky rmarinsky changed the title [codex] prepare 1.14.3 canonical proxy release [codex] prepare 1.14.4 canonical proxy release Jun 1, 2026

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Diduny/Core/Services/PushToTalkService.swift (1)

113-120: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Caps Lock still bypasses the hold-delay path.

handleFlagsChanged(_:) returns to handleCapsLockEvent(...) before processModifierKeyEvent(...), so .capsLock keeps the old immediate start/stop behavior. Meanwhile ShortcutsSettingsView now exposes the delay slider for every PushToTalkKey, which makes this setting look supported for Caps Lock even though it has no effect.

Either route Caps Lock through the delayed path too, or disable/hide the hold-delay UI when .capsLock is selected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Diduny/Core/Services/PushToTalkService.swift` around lines 113 - 120, The
caps-lock branch currently short-circuits to handleCapsLockEvent(...) and
returns, bypassing the hold-delay logic in processModifierKeyEvent(...); change
this so Caps Lock is handled via the same delayed path by either removing the
early return and calling processModifierKeyEvent(isPressed:eventTime:) for
selectedKey == .capsLock, or have handleCapsLockEvent(...) delegate into
processModifierKeyEvent(...) so Caps Lock respects the configured hold-delay;
update handleFlagsChanged(_:) to route Caps Lock through
processModifierKeyEvent(...) (or delegate from handleCapsLockEvent(...) to it)
so the delay slider actually takes effect.
🧹 Nitpick comments (7)
DidunyTests/MeetingChunkStitcherTests.swift (1)

128-155: ⚡ Quick win

Add a test for a single unreadable/empty chunk.

The suite covers all-empty (multi) and leading-empty cases but not a single empty/corrupt chunk. That path currently behaves differently (see stitchSingleChunk), so a dedicated test would pin down the intended contract.

💚 Suggested test
func test_stitch_singleEmptyChunk_throwsAllUnreadable() throws {
    let bad = try writeEmptyFile(name: "chunk_001.wav")
    let target = tmpDir.appendingPathComponent("out.wav")
    XCTAssertThrowsError(try MeetingChunkStitcher.stitch(chunkURLs: [bad], outputURL: target)) { error in
        guard case MeetingChunkStitcher.StitchError.allChunksUnreadable = error else {
            XCTFail("Expected .allChunksUnreadable, got \(error)")
            return
        }
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DidunyTests/MeetingChunkStitcherTests.swift` around lines 128 - 155, Add a
unit test that asserts a single unreadable/empty chunk causes
MeetingChunkStitcher.stitch to throw .allChunksUnreadable; create a single empty
file via writeEmptyFile (e.g., name "chunk_001.wav"), call
MeetingChunkStitcher.stitch(chunkURLs: [bad], outputURL: target) and use
XCTAssertThrowsError to verify the error matches
MeetingChunkStitcher.StitchError.allChunksUnreadable—this will exercise the
stitchSingleChunk path and lock down the intended contract.
Diduny/Core/Services/MeetingRecorderService.swift (2)

121-130: ⚡ Quick win

Consider grouping chunk rotation callback parameters.

The onChunkRotated callback has 6 parameters (flagged by SwiftLint). While functional, bundling related metadata into a struct improves clarity and makes future additions easier.

♻️ Proposed refactor

Define a parameter struct in SystemAudioCaptureService:

struct ChunkRotationInfo {
    let closedIndex: Int
    let closedURL: URL
    let closedAt: Date
    let byteCount: Int64
    let durationSeconds: Double
}

Update the callback signature:

-service.onChunkRotated = { [weak self] closedIndex, closedURL, closedAt, byteCount, durationSeconds in
+service.onChunkRotated = { [weak self] info in
     self?.handleChunkRotated(
-        closedIndex: closedIndex,
-        closedURL: closedURL,
-        closedAt: closedAt,
-        byteCount: byteCount,
-        durationSeconds: durationSeconds,
+        info: info,
         directoryURL: directoryURL
     )
 }

Update handleChunkRotated signature similarly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Diduny/Core/Services/MeetingRecorderService.swift` around lines 121 - 130,
The onChunkRotated callback currently passes six separate parameters; define a
compact struct (e.g., ChunkRotationInfo) in SystemAudioCaptureService containing
closedIndex, closedURL, closedAt, byteCount, and durationSeconds, change the
onChunkRotated signature to accept a single ChunkRotationInfo parameter, and
update all call sites and the MeetingRecorderService handleChunkRotated method
to accept that struct (rename parameter to rotationInfo) and unpack fields as
needed; ensure SystemAudioCaptureService and MeetingRecorderService method
signatures and any related closures are updated consistently to avoid SwiftLint
warnings and simplify future additions.

266-274: ⚡ Quick win

Consider extracting last-chunk duration calculation.

The duration calculation logic (lines 266–274) is sound but dense: it subtracts prior chunk durations from the stitch total or falls back to the overall recording duration. Extracting this into a helper method would improve readability and testability.

♻️ Proposed refactor
private func computeLastChunkDuration(
    stitchResult: MeetingChunkStitcher.Result?,
    manifest: InProgressRecordingManifest,
    overallDuration: TimeInterval
) -> Double {
    if let stitch = stitchResult {
        let priorDurations = manifest.chunks.prefix(manifest.chunks.count - 1)
            .reduce(0.0) { $0 + $1.durationSeconds }
        return max(0, stitch.totalDurationSeconds - priorDurations)
    } else {
        return overallDuration
    }
}

Then replace lines 265–274:

-            let lastDuration: Double
-            if let stitch = stitchResult {
-                // Subtract durations of preceding (closed) chunks from total.
-                let priorDurations = manifest.chunks.prefix(manifest.chunks.count - 1)
-                    .reduce(0.0) { $0 + $1.durationSeconds }
-                lastDuration = max(0, stitch.totalDurationSeconds - priorDurations)
-            } else {
-                // Stitch unavailable (no rotation happened): the whole recording is in chunk_001.
-                lastDuration = duration
-            }
+            let lastDuration = computeLastChunkDuration(
+                stitchResult: stitchResult,
+                manifest: manifest,
+                overallDuration: duration
+            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Diduny/Core/Services/MeetingRecorderService.swift` around lines 266 - 274,
Extract the last-chunk duration calculation into a private helper to improve
readability and testability: create a method like
computeLastChunkDuration(stitchResult: MeetingChunkStitcher.Result?, manifest:
InProgressRecordingManifest, overallDuration: TimeInterval) -> Double that
encapsulates the current logic (if stitchResult non-nil, compute priorDurations
by summing manifest.chunks.prefix(manifest.chunks.count - 1).reduce(0.0) { $0 +
$1.durationSeconds } and return max(0, stitch.totalDurationSeconds -
priorDurations); otherwise return overallDuration), then replace the inline
block that sets lastDuration (which currently references stitchResult, manifest
and duration) with a call to this new helper inside MeetingRecorderService.
Diduny/App/AppDelegate+MeetingTranslationRecording.swift (1)

278-278: ⚡ Quick win

Use NSLog with [Transcription] prefix for cloud API logging.

Lines 278, 307–310, and 312 use Log.transcription.* for real-time translation connection logs in an AppDelegate extension file. Based on learnings, these should use NSLog("[Transcription] ...") formatting for consistency with the expected log output format in AppDelegate extension files.

♻️ Proposed fix
         rtService.onError = { error in
-            Log.transcription.error("Realtime meeting translation error: \(error.localizedDescription)")
+            NSLog("[Transcription] Realtime meeting translation error: %@", error.localizedDescription)
             // Don't stop recording — file recording continues independently
         }
 
         // ...
 
             await MainActor.run {
                 store.isActive = true
             }
-            Log.transcription
-                .info(
-                    "Meeting real-time translation connected successfully (\(sourceLanguage.uppercased()) -> \(targetLanguage.uppercased()))"
-                )
+            NSLog("[Transcription] Meeting real-time translation connected successfully (%@ -> %@)", 
+                  sourceLanguage.uppercased(), targetLanguage.uppercased())
         } catch {
-            Log.transcription.error("Meeting real-time translation FAILED to connect: \(error.localizedDescription)")
+            NSLog("[Transcription] Meeting real-time translation FAILED to connect: %@", error.localizedDescription)
             await MainActor.run {

Based on learnings: prefer NSLog("[Transcription] ...") for cloud API/WebSocket connection logs in AppDelegate extension files rather than Log.transcription.* to maintain consistent log output format.

Also applies to: 307-310, 312-312

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Diduny/App/AppDelegate`+MeetingTranslationRecording.swift at line 278,
Replace the uses of Log.transcription.* in the
AppDelegate+MeetingTranslationRecording extension (e.g., the call
Log.transcription.error("Realtime meeting translation error:
\(error.localizedDescription)") and the other occurrences around the realtime
translation WebSocket/connection handling at the same scope) with
NSLog("[Transcription] ...") calls that preserve the original message text and
interpolated values (for example include error.localizedDescription), so all
cloud API/WS transcription logs use the NSLog("[Transcription] ...") format for
consistent output.
Diduny/App/AppDelegate+MeetingRecording.swift (1)

287-287: ⚡ Quick win

Use NSLog with [Transcription] prefix for cloud API logging.

Lines 287, 302, and 304 use Log.transcription.* for real-time transcription connection logs in an AppDelegate extension file. Based on learnings, these should use NSLog("[Transcription] ...") formatting for consistency with the expected log output format in AppDelegate extension files.

♻️ Proposed fix
         rtService.onError = { error in
-            Log.transcription.error("Realtime transcription error: \(error.localizedDescription)")
+            NSLog("[Transcription] Realtime transcription error: %@", error.localizedDescription)
             // Don't stop recording — file recording continues independently
         }
 
         // ...
 
             await MainActor.run {
                 store.isActive = true
             }
-            Log.transcription.info("Meeting real-time transcription connected successfully")
+            NSLog("[Transcription] Meeting real-time transcription connected successfully")
         } catch {
-            Log.transcription.error("Meeting real-time transcription FAILED to connect: \(error.localizedDescription)")
+            NSLog("[Transcription] Meeting real-time transcription FAILED to connect: %@", error.localizedDescription)
             await MainActor.run {

Based on learnings: prefer NSLog("[Transcription] ...") for cloud API/WebSocket connection logs in AppDelegate extension files rather than Log.transcription.* to maintain consistent log output format.

Also applies to: 302-302, 304-304

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Diduny/App/AppDelegate`+MeetingRecording.swift at line 287, Replace the
Log.transcription.* calls in the AppDelegate+MeetingRecording.swift extension
with NSLog using a "[Transcription]" prefix so cloud API/WebSocket logs match
the expected format; specifically, change instances like
Log.transcription.error("Realtime transcription error:
\(error.localizedDescription)") to NSLog("[Transcription] Realtime transcription
error: %@", error.localizedDescription) and similarly convert the other
Log.transcription.* invocations to NSLog("[Transcription] ...") preserving the
original message text and any interpolated values.
Diduny/Core/Services/PushToTalkService.swift (1)

175-177: ⚡ Quick win

Use NSLog for these new state-transition logs.

These messages are tracking push-to-talk state changes, so they should follow the repo's NSLog("[AppState] ...") convention instead of Log.app.info(...). As per coding guidelines "Use NSLog() for logging with prefixes: [Diduny] for AppDelegate flow, [Transcription] for cloud API calls, [AppState] for state changes".

Also applies to: 181-183, 187-206

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Diduny/Core/Services/PushToTalkService.swift` around lines 175 - 177, Replace
Log.app.info(...) calls used for push-to-talk state-transition messages in
PushToTalkService (the guard using hasStartedAfterHold and the subsequent
similar logs at the same block and lines 181-183, 187-206) with NSLog() using
the [AppState] prefix; e.g., change any
Log.app.info("\(self.selectedKey.displayName) ...") to NSLog("[AppState]
\(self.selectedKey.displayName) ...") so all state-change logs follow the repo
convention for AppState messages.
Diduny/App/AppDelegate+Hotkeys.swift (1)

96-100: ⚡ Quick win

Switch these new AppDelegate state logs to NSLog.

These are AppDelegate state-change logs, so they should use the repo's NSLog("[AppState] ...") format instead of Log.app.info(...). As per coding guidelines "Use NSLog() for logging with prefixes: [Diduny] for AppDelegate flow, [Transcription] for cloud API calls, [AppState] for state changes".

Also applies to: 160-164

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Diduny/App/AppDelegate`+Hotkeys.swift around lines 96 - 100, Replace the
Log.app.info calls in the AppDelegate state-change handlers (e.g.,
pushToTalkHoldStartDelayChanged(_:), and the similar handler around lines
160-164) with NSLog using the "[AppState]" prefix; specifically, keep the same
message text/formatting (e.g., the formatted delay string) but call
NSLog("[AppState] Dictation modifier hold delay changed to: ...") instead of
Log.app.info so AppDelegate state transitions follow the repo logging
convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Diduny/App/AppDelegate.swift`:
- Around line 219-305: The willSleep/didWake closures run on a background thread
but currently call AppDelegate's main-isolated methods
(flushActiveRecordingForSleep and handleWakeAfterRecordingInterrupt) and mutate
main-actor state (recordingWasInterruptedBySleep, meetingActivityToken,
meetingTranslationActivityToken), which can deadlock or defer activity token
release; make the sleep/wake bookkeeping actor-independent by extracting
non-main-isolated helpers: add a nonisolated method (or standalone helper) to
perform the synchronous flush logic that returns flushedURL/ok and the
recordingId without touching main-actor properties, and add a nonisolated
function to synchronously end activity tokens (or move token storage into a
thread-safe, non-@MainActor container) so
ProcessInfo.processInfo.endActivity(...) can be called before the willSleep
handler returns; update setupSleepHandling to call
SleepFlushCoordinator.flushCurrentChunk -> the new nonisolated flush helper, and
keep only UI/state updates (e.g., handleWakeAfterRecordingInterrupt and any
mutation of recordingWasInterruptedBySleep) dispatched to the MainActor
asynchronously after the background work completes.

In `@Diduny/Core/Services/MeetingChunkStitcher.swift`:
- Around line 72-91: stitchSingleChunk currently copies the file even when
AVAudioFile reading fails (duration == 0), causing corrupt/empty chunks to be
persisted; fix this by checking computed duration before copying in
stitchSingleChunk and throw StitchError.allChunksUnreadable if duration == 0 (so
no copy occurs), and additionally update MeetingRecorderService.stopRecording()
to gate persistence/transcription on stitchResult?.appendedChunkCount > 0 (or
explicitly handle StitchError.allChunksUnreadable in the fallback that uses
capturedChunkURLs.last) to ensure unreadable chunks are not saved or
transcribed.

In `@Diduny/Core/Services/PushToTalkService.swift`:
- Around line 93-95: resetHandsFreeMode() currently clears active hold state by
setting hasStartedAfterHold = false which breaks hold-to-record flows because
AppDelegate+Hotkeys calls this on tap-count/hold-delay changes and the
subsequent key-up will early-return in onKeyUp; remove the hasStartedAfterHold
reset from resetHandsFreeMode() and instead introduce a new method (e.g.,
clearFullHandsFreeState() or resetAllHandsFreeState()) that cancels pending
timers and also clears hasStartedAfterHold for callers that truly need a full
reset; update callers that only change configuration (like AppDelegate+Hotkeys)
to call the lighter resetHandsFreeMode() while callers that need a full wipe
call the new helper.

In `@Diduny/Core/Services/SystemAudioCaptureService.swift`:
- Around line 440-448: The race occurs because isCapturing is only cleared after
synchronizedTeardown(), allowing in-flight SCStream callbacks on
streamOutputQueue to enqueue onto mixerQueue (and the microphone tap path) after
teardown; to fix, set isCapturing = false before calling synchronizedTeardown()
in synchronousFlushForSleep(), and add a guard at the start of the
microphone/tap callback (the mic path that enqueues to mixerQueue) to drop any
buffers when isCapturing is false so late callbacks are gated and cannot enqueue
work after teardown.

In `@Diduny/Core/Storage/InProgressRecordingStore.swift`:
- Around line 20-28: The initializer for InProgressRecordingStore should
validate the applicationSupportDirectory lookup and surface directory-creation
errors instead of force-unwrapping and silencing failures: change
init(baseDirectory:fileManager:) to a throwing initializer (init(...) throws),
replace the force-unwrap of fileManager.urls(for: .applicationSupportDirectory,
in: .userDomainMask).first! with a guard-let that throws a descriptive error
(e.g., ApplicationSupportDirectoryUnavailable), compute self.baseDirectory from
the provided baseDirectory or the validated appSupport path, and replace try?
fileManager.createDirectory(...) with try fileManager.createDirectory(...) so
directory creation failures propagate; update any call sites (notably the static
let shared = InProgressRecordingStore() singleton) to handle or propagate the
thrown error per the review note.

In `@DidunyTests/RecordingModelMigrationTests.swift`:
- Around line 17-27: Rename the single-letter local variables to meet
SwiftLint's identifier_name rule: in the iso8601 JSONDecoder closure rename the
variable `d` to a descriptive ≥3-char name (e.g., `decoder`) and update its
return reference; in the iso8601Encoder JSONEncoder closure rename `e` to a
descriptive ≥3-char name (e.g., `encoder`) and update its return reference; and
rename the other single-letter `r` (used elsewhere around the
RecordingModelMigrationTests file) to a ≥3-char name (e.g., `result` or
`reader`) and update all subsequent `r.` references accordingly so all usages
compile and lint cleanly.

---

Outside diff comments:
In `@Diduny/Core/Services/PushToTalkService.swift`:
- Around line 113-120: The caps-lock branch currently short-circuits to
handleCapsLockEvent(...) and returns, bypassing the hold-delay logic in
processModifierKeyEvent(...); change this so Caps Lock is handled via the same
delayed path by either removing the early return and calling
processModifierKeyEvent(isPressed:eventTime:) for selectedKey == .capsLock, or
have handleCapsLockEvent(...) delegate into processModifierKeyEvent(...) so Caps
Lock respects the configured hold-delay; update handleFlagsChanged(_:) to route
Caps Lock through processModifierKeyEvent(...) (or delegate from
handleCapsLockEvent(...) to it) so the delay slider actually takes effect.

---

Nitpick comments:
In `@Diduny/App/AppDelegate`+Hotkeys.swift:
- Around line 96-100: Replace the Log.app.info calls in the AppDelegate
state-change handlers (e.g., pushToTalkHoldStartDelayChanged(_:), and the
similar handler around lines 160-164) with NSLog using the "[AppState]" prefix;
specifically, keep the same message text/formatting (e.g., the formatted delay
string) but call NSLog("[AppState] Dictation modifier hold delay changed to:
...") instead of Log.app.info so AppDelegate state transitions follow the repo
logging convention.

In `@Diduny/App/AppDelegate`+MeetingRecording.swift:
- Line 287: Replace the Log.transcription.* calls in the
AppDelegate+MeetingRecording.swift extension with NSLog using a
"[Transcription]" prefix so cloud API/WebSocket logs match the expected format;
specifically, change instances like Log.transcription.error("Realtime
transcription error: \(error.localizedDescription)") to NSLog("[Transcription]
Realtime transcription error: %@", error.localizedDescription) and similarly
convert the other Log.transcription.* invocations to NSLog("[Transcription]
...") preserving the original message text and any interpolated values.

In `@Diduny/App/AppDelegate`+MeetingTranslationRecording.swift:
- Line 278: Replace the uses of Log.transcription.* in the
AppDelegate+MeetingTranslationRecording extension (e.g., the call
Log.transcription.error("Realtime meeting translation error:
\(error.localizedDescription)") and the other occurrences around the realtime
translation WebSocket/connection handling at the same scope) with
NSLog("[Transcription] ...") calls that preserve the original message text and
interpolated values (for example include error.localizedDescription), so all
cloud API/WS transcription logs use the NSLog("[Transcription] ...") format for
consistent output.

In `@Diduny/Core/Services/MeetingRecorderService.swift`:
- Around line 121-130: The onChunkRotated callback currently passes six separate
parameters; define a compact struct (e.g., ChunkRotationInfo) in
SystemAudioCaptureService containing closedIndex, closedURL, closedAt,
byteCount, and durationSeconds, change the onChunkRotated signature to accept a
single ChunkRotationInfo parameter, and update all call sites and the
MeetingRecorderService handleChunkRotated method to accept that struct (rename
parameter to rotationInfo) and unpack fields as needed; ensure
SystemAudioCaptureService and MeetingRecorderService method signatures and any
related closures are updated consistently to avoid SwiftLint warnings and
simplify future additions.
- Around line 266-274: Extract the last-chunk duration calculation into a
private helper to improve readability and testability: create a method like
computeLastChunkDuration(stitchResult: MeetingChunkStitcher.Result?, manifest:
InProgressRecordingManifest, overallDuration: TimeInterval) -> Double that
encapsulates the current logic (if stitchResult non-nil, compute priorDurations
by summing manifest.chunks.prefix(manifest.chunks.count - 1).reduce(0.0) { $0 +
$1.durationSeconds } and return max(0, stitch.totalDurationSeconds -
priorDurations); otherwise return overallDuration), then replace the inline
block that sets lastDuration (which currently references stitchResult, manifest
and duration) with a call to this new helper inside MeetingRecorderService.

In `@Diduny/Core/Services/PushToTalkService.swift`:
- Around line 175-177: Replace Log.app.info(...) calls used for push-to-talk
state-transition messages in PushToTalkService (the guard using
hasStartedAfterHold and the subsequent similar logs at the same block and lines
181-183, 187-206) with NSLog() using the [AppState] prefix; e.g., change any
Log.app.info("\(self.selectedKey.displayName) ...") to NSLog("[AppState]
\(self.selectedKey.displayName) ...") so all state-change logs follow the repo
convention for AppState messages.

In `@DidunyTests/MeetingChunkStitcherTests.swift`:
- Around line 128-155: Add a unit test that asserts a single unreadable/empty
chunk causes MeetingChunkStitcher.stitch to throw .allChunksUnreadable; create a
single empty file via writeEmptyFile (e.g., name "chunk_001.wav"), call
MeetingChunkStitcher.stitch(chunkURLs: [bad], outputURL: target) and use
XCTAssertThrowsError to verify the error matches
MeetingChunkStitcher.StitchError.allChunksUnreadable—this will exercise the
stitchSingleChunk path and lock down the intended contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae6bdda5-44c0-4a88-bcd6-7262f7c5de4d

📥 Commits

Reviewing files that changed from the base of the PR and between 748755f and d7e1645.

📒 Files selected for processing (25)
  • Diduny.xcodeproj/project.pbxproj
  • Diduny.xcodeproj/xcshareddata/xcschemes/Diduny TEST.xcscheme
  • Diduny/App/AppDelegate+Hotkeys.swift
  • Diduny/App/AppDelegate+MeetingRecording.swift
  • Diduny/App/AppDelegate+MeetingTranslationRecording.swift
  • Diduny/App/AppDelegate.swift
  • Diduny/Core/Models/Recording.swift
  • Diduny/Core/Protocols/ServiceProtocols.swift
  • Diduny/Core/Services/AuthService.swift
  • Diduny/Core/Services/MeetingChunkStitcher.swift
  • Diduny/Core/Services/MeetingRecorderService.swift
  • Diduny/Core/Services/PushToTalkService.swift
  • Diduny/Core/Services/SleepFlushCoordinator.swift
  • Diduny/Core/Services/SystemAudioCaptureService.swift
  • Diduny/Core/Storage/InProgressRecordingManifest.swift
  • Diduny/Core/Storage/InProgressRecordingStore.swift
  • Diduny/Core/Storage/RecordingsLibraryStorage.swift
  • Diduny/Core/Storage/SettingsStorage.swift
  • Diduny/Features/Settings/ShortcutsSettingsView.swift
  • DidunyTests/InProgressRecordingStoreTests.swift
  • DidunyTests/MeetingChunkStitcherTests.swift
  • DidunyTests/PushToTalkServiceTests.swift
  • DidunyTests/RecordingModelMigrationTests.swift
  • DidunyTests/SleepFlushCoordinatorTests.swift
  • project.yml

Comment thread Diduny/App/AppDelegate.swift
Comment thread Diduny/Core/Services/MeetingChunkStitcher.swift
Comment thread Diduny/Core/Services/PushToTalkService.swift Outdated
Comment thread Diduny/Core/Services/SystemAudioCaptureService.swift Outdated
Comment thread Diduny/Core/Storage/InProgressRecordingStore.swift Outdated
Comment thread DidunyTests/RecordingModelMigrationTests.swift
@rmarinsky rmarinsky merged commit 656e99f into main Jun 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:patch Bug fix / internal — bumps patch version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant