Repository navigation
Keep every Gmail connection fresh - #137
Conversation
|
Warning Review limit reached
Next review available in: 12 seconds Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds per-connection Gmail mailbox freshness coordination across launch, foreground, polling, push wakeups, and manual refresh. Sync phases and successful timestamps are persisted, surfaced in the sidebar, serialized per connection, and validated with synchronization and push relay tests. ChangesGmail mailbox freshness
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant AccountView
participant MailboxFreshnessViewModel
participant GmailMailboxConnectionAdapter
participant GmailPushWakeupHandler
AccountView->>MailboxFreshnessViewModel: synchronize authorized connections
MailboxFreshnessViewModel->>GmailMailboxConnectionAdapter: syncInbox and backfill
GmailPushWakeupHandler->>GmailMailboxConnectionAdapter: syncRecentInbox for routed connection
GmailPushWakeupHandler-->>AccountView: publish mailbox sync status
AccountView->>AccountView: reload observed metadata
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e5be7e480
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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 `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift`:
- Around line 3453-3463: Update the retry branch after refreshConnections()
succeeds to clear errorMessage and execute the same selected-connection
reconciliation, pushStatusMessages, and push-watch refresh performed by the
normal success path before returning true; keep the failure branch reporting
originalError and returning false.
In `@apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift`:
- Around line 1368-1373: Update the notification-rule loading flow in the Gmail
push relay handler so failures to load rules return false rather than true,
including the failClosed result and the subsequent nil-rules guard. Preserve the
existing true behavior when notificationRules is absent, and ensure unprocessed
pushes do not advance the watermark or suppress retry/reconciliation.
- Around line 1344-1354: Update the error path around
completeWithGenericFallback() so fallback delivery does not advance
latestSyncedHistoryId or otherwise persist a new sinceHistoryId after
syncRecentInbox fails. Preserve publishing the failure status and rule checks,
but leave the wakeup eligible for retry or reconciliation while delivering the
generic fallback.
In `@apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift`:
- Around line 1165-1178: Consolidate the duplicated sync-gate lifecycle by
adding a withLock helper to MailboxConnectionSyncGate that acquires the
connection ID, executes an async throwing operation, and defers synchronous
release. Update continueHistoricalBackfill, syncInbox, and syncRecentInbox to
use this helper while preserving their existing cancellation checks, results,
and error propagation.
- Around line 60-68: Update MailboxConnection.acquire to use cancellation-aware
waiting instead of bare withCheckedContinuation. Ensure a cancelled task is
removed from waiters[connectionId] and resumes or exits promptly, without
consuming a FIFO slot; preserve the existing lock acquisition and waiter
ordering behavior for non-cancelled tasks.
In `@apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift`:
- Around line 723-733: Update the test’s UserDefaultsMailboxSyncSuccessStore
setup and both clear calls to use the suite-scoped defaults instance by
constructing it with the existing defaults dependency. Ensure the test no longer
reads from or writes to UserDefaults.standard while preserving the current
cleanup behavior.
🪄 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: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 3b90dd97-8ff2-4678-b188-92cc4ff7939e
📒 Files selected for processing (7)
.changeset/gmail-mailbox-freshness.mdREADME.mdapps/unwired-mail/unwired-mail/Features/Account/AccountView.swiftapps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swiftapps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swiftapps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swiftapps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
@codex review |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift (1)
3439-3450: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winDo not retry canceled loads.
The catch at Line 3441 also handles cancellation, so a canceled
AccountViewtask can issue a secondrefreshConnections()request and publish a spurious cancellation error. SincecompleteLoadingConnections()invokes a helper that swallows cancellation, check cancellation before retrying and after completion without updatingerrorMessage.Proposed fix
do { try await refreshConnections() await completeLoadingConnections() + try Task.checkCancellation() return true + } catch is CancellationError { + return false } catch { + guard !Task.isCancelled else { return false } let originalError = error do { + try Task.checkCancellation() try await refreshConnections() await completeLoadingConnections() + try Task.checkCancellation() return true + } catch is CancellationError { + return false } catch { + guard !Task.isCancelled else { return false } errorMessage = originalError.localizedDescription return false } }🤖 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 `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift` around lines 3439 - 3450, Update the error handling around refreshConnections in AccountView so cancellation is detected before issuing the retry and again after completeLoadingConnections, returning without setting errorMessage in either case. Preserve the existing retry and original-error reporting behavior for non-cancellation failures.
🤖 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 `@apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift`:
- Around line 62-108: Update acquire and withLock so acquire reports whether the
caller actually obtained the lock, including cancellation paths, and withLock
only invokes release when acquisition succeeded. Preserve normal serialization
for successful acquisitions while ensuring cancelled waiters cannot release or
transfer another task’s lock; also make cancellation registration and waiter
insertion race-safe so cancellation cannot be lost before the waiter is
registered.
---
Outside diff comments:
In `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift`:
- Around line 3439-3450: Update the error handling around refreshConnections in
AccountView so cancellation is detected before issuing the retry and again after
completeLoadingConnections, returning without setting errorMessage in either
case. Preserve the existing retry and original-error reporting behavior for
non-cancellation failures.
🪄 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: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 3e7a123b-1839-47c8-94c1-a620b6e11dfe
📒 Files selected for processing (4)
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swiftapps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swiftapps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swiftapps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 552a422bb2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift (2)
1174-1191: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winSeed persisted state before constructing the ViewModel.
Line 1175 creates the ViewModel before Line 1177 saves the stale timestamp, so this does not cover removal of state restored at startup. Seed the store first, then construct the ViewModel and apply the first authoritative connection update.
As per coding guidelines, “Define verifiable success criteria and validate each implementation step; add regression tests for bug fixes and tests for validation behavior.”
🤖 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 `@apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift` around lines 1174 - 1191, Update testMailboxFreshnessClearsLastSuccessRemovedBeforeViewModelStarts so the successStore.save call seeds the persisted timestamp before makeMailboxFreshnessFixture constructs the ViewModel. Then obtain the fixture, apply the initial authoritative update via updateConnections, and retain the assertion that the removed connection’s state is cleared.Source: Coding guidelines
1322-1335: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winMake the gate-contention check deterministic.
pollAttemptedonly proves the task started, not that it reachedMailboxConnectionSyncGate.acquire. Replace theTask.yield()loop with a test hook that signals when the poll is actually queued, then assertpollAcquiredis still false beforerelease.🤖 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 `@apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift` around lines 1322 - 1335, Replace the fixed Task.yield loop in the gate-contention test with a synchronization hook that fulfills when the poll reaches or queues at MailboxConnectionSyncGate.acquire. Await that hook before checking probe.pollAcquired, keeping the assertion false while the push still holds the gate, then release the gate afterward.apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift (2)
1981-1989: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winConcurrent pin toggles on different messages can misattribute errors.
togglePin(PinViewModel, lines 2374-2396) permits concurrent toggles across differentmessageIds — only same-message reentry is guarded viaupdatingMessageIds. The caller at lines 1982-1989 awaits its own toggle, then reads the sharedpinViewModel.errorMessageto decide whether to showreaderErrorMessage. If message A's toggle succeeds while a concurrently-started toggle for message B fails, A's completion sees B's leftovererrorMessageand incorrectly shows a "Message action failed" alert for a successful action.Have
togglePinreturn the outcome directly instead of relying on shared mutable state read after the fact.🐛 Proposed fix to thread the toggle result back to the caller
- func togglePin(_ messageId: StableProviderMessageIdentity) async { + `@discardableResult` + func togglePin(_ messageId: StableProviderMessageIdentity) async -> String? { guard !updatingMessageIds.contains(messageId) else { return } ... do { try await service.setPinned( shouldPin, messageId: messageId, session: session ) completedToggleGenerations[messageId, default: 0] += 1 + return nil } catch is CancellationError { setPinnedLocally(wasPinned, messageId: messageId) + return nil } catch { setPinnedLocally(wasPinned, messageId: messageId) errorMessage = error.localizedDescription + return error.localizedDescription } }togglePin: { Task { - await togglePin(message.id) - if let errorMessage = pinViewModel.errorMessage { + if let errorMessage = await pinViewModel.togglePin(message.id) { readerErrorMessage = errorMessage } } }Also applies to: 2062-2065, 2330-2417
🤖 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 `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift` around lines 1981 - 1989, Update PinViewModel.togglePin to return the individual toggle outcome, and have each caller, including the togglePin closure near the reader action and the other call sites, use that returned result to set readerErrorMessage rather than reading shared pinViewModel.errorMessage after awaiting. Preserve the existing same-message concurrency guard and shared error state behavior where otherwise required, but ensure concurrent toggles cannot attribute one message’s failure to another.
3267-3286: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winCancellation is swallowed instead of stopping the prefetch loop.
The catch-all at line 3281-3283 also catches
CancellationError, so cancellingbodyPrefetchTask(e.g. a newer prefetch superseding it) doesn't stop iteration — the loop continues to the next connection. This is inconsistent with thecatch is CancellationError { return ... }pattern used elsewhere in this file (e.g. lines 2961-2963, 2993-2995, 3062-3064).🔧 Proposed fix to stop the loop on cancellation
bodyPrefetchTask = Task { [weak self] in guard let self else { return } for connection in connections { + guard !Task.isCancelled else { return } do { try await bodyPrefetcher.prefetchMessageBodies( connection: connection, pinnedMessageIds: navigationSnapshot.pinnedMessageIds, referenceDate: Date(), session: self.session ) + } catch is CancellationError { + return } catch { // Prefetch is best effort and must not block cached mailbox use. } } }🤖 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 `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift` around lines 3267 - 3286, Update the Task loop in startBodyPrefetch to handle CancellationError separately and return immediately when cancellation occurs, before the existing best-effort handling for other errors. Preserve the current behavior of continuing to the next connection for non-cancellation failures.
🤖 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.
Outside diff comments:
In `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift`:
- Around line 1981-1989: Update PinViewModel.togglePin to return the individual
toggle outcome, and have each caller, including the togglePin closure near the
reader action and the other call sites, use that returned result to set
readerErrorMessage rather than reading shared pinViewModel.errorMessage after
awaiting. Preserve the existing same-message concurrency guard and shared error
state behavior where otherwise required, but ensure concurrent toggles cannot
attribute one message’s failure to another.
- Around line 3267-3286: Update the Task loop in startBodyPrefetch to handle
CancellationError separately and return immediately when cancellation occurs,
before the existing best-effort handling for other errors. Preserve the current
behavior of continuing to the next connection for non-cancellation failures.
In `@apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift`:
- Around line 1174-1191: Update
testMailboxFreshnessClearsLastSuccessRemovedBeforeViewModelStarts so the
successStore.save call seeds the persisted timestamp before
makeMailboxFreshnessFixture constructs the ViewModel. Then obtain the fixture,
apply the initial authoritative update via updateConnections, and retain the
assertion that the removed connection’s state is cleared.
- Around line 1322-1335: Replace the fixed Task.yield loop in the
gate-contention test with a synchronization hook that fulfills when the poll
reaches or queues at MailboxConnectionSyncGate.acquire. Await that hook before
checking probe.pollAcquired, keeping the assertion false while the push still
holds the gate, then release the gate afterward.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: d7b5c098-6836-4597-95b7-094635ed7c80
📒 Files selected for processing (3)
README.mdapps/unwired-mail/unwired-mail/Features/Account/AccountView.swiftapps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift (4)
275-305: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winClear stale authorization-required status after reauthorization.
updateConnections()preserves the existing status for IDs that remain present. When a connection changes from.requiredto.authorized,status(for:)returns that stale.authorizationRequiredphase until a later sync rewrites it. This is especially visible when the connection cannot synchronize immediately.Reset the phase to
.idleon a required-to-authorized transition while preserving the stored successful timestamp.🤖 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 `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift` around lines 275 - 305, The updateConnections method must reset a connection’s stale authorizationRequired phase to idle when its authorizationState transitions from required to authorized, while retaining its stored successful timestamp. Compare the previous knownConnections state with the incoming connections and update the corresponding statuses entry without affecting other phases or connections.
467-478: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winDo not roll back newer freshness timestamps after backfill failure.
If a push or another sync records a newer success while this backfill is running, the cancellation/failure paths restore
priorStatusand overwrite that newerlastSuccessfulSyncAtin memory. Becausestatus(for:)prefersstatusesover the store, the sidebar can continue showing stale freshness data.Use the current status when completing the backfill and preserve the newest successful timestamp; only restore the prior phase when this backfill still owns the connection.
Also applies to: 604-610
🤖 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 `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift` around lines 467 - 478, Update the cancellation and failure handling around the historical backfill flow and its corresponding logic near the second referenced block to read the current connection status before completing. Preserve the newest successful timestamp from any push or concurrent sync, and restore priorStatus only when historicalBackfills[connection.id]?.id still matches backfillId; otherwise leave the newer status unchanged.
393-413: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winGuard sync result handling by
syncId.After
updateConnections()orcancelAll()removes an in-flight entry, the canceled task can still resume here and write.idleor.failure. If a replacement sync starts first, the old task can overwrite its.syncingor successful state.Require
inFlightSyncs[connection.id]?.id == syncIdplus current-session/connection checks before mutating status or persistence. Add a regression test for cancellation followed by a replacement sync.Suggested direction
+let ownsSync = inFlightSyncs[connection.id]?.id == syncId +guard ownsSync, isSessionCurrent(session), knownConnections[connection.id] != nil else { + throw CancellationError() +} removeSync(connectionId: connection.id, syncId: syncId)Apply the same ownership check in each catch branch before updating
statuses.As per coding guidelines, add regression tests for bug fixes.
🤖 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 `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift` around lines 393 - 413, Guard both cancellation catch branches in the sync flow around the visible error handling with an ownership check requiring inFlightSyncs[connection.id]?.id == syncId and valid current-session/connection state before mutating statuses or persistence; ensure stale tasks perform no updates after removal or replacement by updateConnections() or cancelAll(). Add a regression test covering cancellation followed by a replacement sync and verifying the old task cannot overwrite the replacement state.Source: Coding guidelines
354-356: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick winDefer backfill until after sync completes
loadAfterConnectionChange(..., synchronizes: true)currently starts historical backfill beforesync(), so the sync path cancels and may restart the same work. PassstartsBackfill: falsehere and letsync()schedule it afterward.🤖 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 `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift` around lines 354 - 356, Update the loadAfterConnectionChange call in the synchronizes=true path to pass startsBackfill: false, preventing historical backfill from starting before sync. Keep sync() responsible for scheduling the backfill after the in-flight sync completes.
🤖 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.
Outside diff comments:
In `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift`:
- Around line 275-305: The updateConnections method must reset a connection’s
stale authorizationRequired phase to idle when its authorizationState
transitions from required to authorized, while retaining its stored successful
timestamp. Compare the previous knownConnections state with the incoming
connections and update the corresponding statuses entry without affecting other
phases or connections.
- Around line 467-478: Update the cancellation and failure handling around the
historical backfill flow and its corresponding logic near the second referenced
block to read the current connection status before completing. Preserve the
newest successful timestamp from any push or concurrent sync, and restore
priorStatus only when historicalBackfills[connection.id]?.id still matches
backfillId; otherwise leave the newer status unchanged.
- Around line 393-413: Guard both cancellation catch branches in the sync flow
around the visible error handling with an ownership check requiring
inFlightSyncs[connection.id]?.id == syncId and valid current-session/connection
state before mutating statuses or persistence; ensure stale tasks perform no
updates after removal or replacement by updateConnections() or cancelAll(). Add
a regression test covering cancellation followed by a replacement sync and
verifying the old task cannot overwrite the replacement state.
- Around line 354-356: Update the loadAfterConnectionChange call in the
synchronizes=true path to pass startsBackfill: false, preventing historical
backfill from starting before sync. Keep sync() responsible for scheduling the
backfill after the in-flight sync completes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 970b4308-24b2-4d90-b75e-aee06bdf2704
📒 Files selected for processing (3)
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swiftapps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swiftapps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
|
@codex review |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 216677cf89
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| session: session | ||
| ) | ||
| return result.mailboxResult(connectionId: connection.id) | ||
| try await syncGate.withLock(connection.id) { |
There was a problem hiding this comment.
Let push wakeups preempt historical backfill
When a historical backfill is running for a large mailbox, this takes the same connection-wide gate that syncRecentInbox uses for Gmail push wakeups, so a later push for the same connection waits for the full backfill instead of interrupting it. Because APNs background processing has a short time budget, this can make new-mail reconciliation and notifications miss the wake window whenever a long backfill is in progress; the backfill path needs to be cancellable/preemptible before it holds the shared gate.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Deferred to #140. Safe push preemption needs explicit ownership and priority handling for the active backfill; this focused follow-up defines the required behavior. This thread remains open.
| return | ||
| } | ||
| guard !Task.isCancelled, isSessionCurrent(session) else { return } | ||
| await synchronize(connections: connections()) |
There was a problem hiding this comment.
Avoid polling during historical categorization
This active poll can run while the user has a historical recategorization in progress, but categorizeHistorical is not protected by the new sync gate and saves a full snapshot of the connection's messages. If the poll persists newer mailbox metadata after categorization has loaded its old snapshot but before it saves, the categorization save can overwrite/delete those newly synced records, so freshness syncs need to be serialized with or paused during historical categorization.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Deferred to #141. Freshness sync and historical categorization need a deliberate shared persistence policy; this focused follow-up defines the required behavior. This thread remains open.
|
@codex review |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift (1)
1360-1382: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winRevalidate the route before recording sync success.
A session/connection can change after
syncRecentInboxacceptsshouldPersistbut before it returns. The guard at Line 1382 prevents watermark advancement, but Lines 1360-1381 already persist and publish a stale successful sync. GuardcurrentWatchForRoute()before savingsuccessfulSyncAtand add a session-change-after-sync regression test.Proposed fix
+ guard currentWatchForRoute() != nil else { return false } let successfulSyncAt = Date() successStore.save( successfulSyncAt,As per coding guidelines, “add regression tests for bug fixes and tests for validation behavior.”
🤖 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 `@apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift` around lines 1360 - 1382, The sync-success path currently records and publishes success before validating that the route/session is still current. In the flow around syncRecentInbox and currentWatchForRoute(), move the current-route guard before successfulSyncAt creation, successStore.save, fallback scheduling, and publishSyncStatus; return false when the route changed. Add a regression test covering a session/connection change after sync acceptance but before completion, asserting no stale watermark or success status is persisted.Source: Coding guidelines
🤖 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.
Outside diff comments:
In `@apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift`:
- Around line 1360-1382: The sync-success path currently records and publishes
success before validating that the route/session is still current. In the flow
around syncRecentInbox and currentWatchForRoute(), move the current-route guard
before successfulSyncAt creation, successStore.save, fallback scheduling, and
publishSyncStatus; return false when the route changed. Add a regression test
covering a session/connection change after sync acceptance but before
completion, asserting no stale watermark or success status is persisted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 845c9e1e-6483-4c19-bfb7-ea3fab03d676
📒 Files selected for processing (3)
apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swiftapps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swiftapps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift
|
Codex Review: Didn't find any major issues. Chef's kiss. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift (2)
1129-1132: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAssert polling coverage per connection.
A total count equal to
fixture.connections.countstill passes if one mailbox is synchronized twice and another is skipped. Assert the recorded IDs as a set and retain the count check for duplicate detection.Proposed assertions
let receivedDurations = await sleeper.receivedDurations() - let syncCallCount = await fixture.service.syncCallCount() XCTAssertEqual(receivedDurations, [.seconds(300), .seconds(300)]) - XCTAssertEqual(syncCallCount, fixture.connections.count) + let syncedConnectionIds = await fixture.service.syncedConnectionIds() + XCTAssertEqual( + Set(syncedConnectionIds), + Set(fixture.connections.map(\.id)) + ) + XCTAssertEqual(syncedConnectionIds.count, fixture.connections.count)🤖 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 `@apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift` around lines 1129 - 1132, Update the test assertions around sleeper.receivedDurations, syncCallCount, and fixture.service to also retrieve the recorded synchronization IDs and compare them as a set with the expected connection IDs. Keep the existing syncCallCount equality assertion so duplicate synchronizations remain detectable.
1373-1408: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTrack the cancelled and next waiters separately.
Because both waiters write to the same
pollAcquiredboolean, the test can pass even if the cancelled waiter acquires after release. Use separate probes—or record acquisition labels—and assert the cancelled probe remains false while the next waiter becomes true.🤖 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 `@apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift` around lines 1373 - 1408, Update testMailboxConnectionSyncGateKeepsLockWhenQueuedTaskIsCancelled to track the cancelled waiter and next waiter with separate acquisition probes or labels. Assert the cancelled waiter never records acquisition after cancellation, while the next waiter remains false before release and true after release.
🤖 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 `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift`:
- Around line 374-375: Update the sync completion flow around task.value and
Task.checkCancellation so cancellation after a successful task result cannot
skip the successful-sync bookkeeping or successStore.save. Ensure the
canceled-owner path preserves the returned result’s lastSuccessfulSyncAt
metadata, and add regression coverage for a first caller canceling after the
underlying sync succeeds.
In `@apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift`:
- Around line 1049-1055: Reorder the cancellation assertions in this test so the
cancelled caller’s `first.value` is awaited and verified to throw
CancellationError immediately after `first.cancel()`, before calling
`fixture.service.releaseSync()`. Keep the shared sync release after cancellation
handling has completed, then preserve the remaining synchronization and
assertions.
---
Outside diff comments:
In `@apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift`:
- Around line 1129-1132: Update the test assertions around
sleeper.receivedDurations, syncCallCount, and fixture.service to also retrieve
the recorded synchronization IDs and compare them as a set with the expected
connection IDs. Keep the existing syncCallCount equality assertion so duplicate
synchronizations remain detectable.
- Around line 1373-1408: Update
testMailboxConnectionSyncGateKeepsLockWhenQueuedTaskIsCancelled to track the
cancelled waiter and next waiter with separate acquisition probes or labels.
Assert the cancelled waiter never records acquisition after cancellation, while
the next waiter remains false before release and true after release.
🪄 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: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 65e719b3-36bf-4eeb-97bd-75930f5ab731
📒 Files selected for processing (2)
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swiftapps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 66844e6962
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
|
Codex Review: Didn't find any major issues. Chef's kiss. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Closes #59
Summary
Validation
mise exec -- pnpm install --frozen-lockfilemise exec -- pnpm lintmise exec -- pnpm formatmise exec -- pnpm turbo run check-typesmise exec -- pnpm testmise exec -- pnpm fallowmise exec -- zsh scripts/check-apple-lint.zshxcodebuild test -project apps/unwired-mail/unwired-mail.xcodeproj -scheme unwired-mail -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:unwired-mailTests/GmailMessageMetadataServiceTestsxcodebuild test -project apps/unwired-mail/unwired-mail.xcodeproj -scheme unwired-mail -destination 'platform=iOS Simulator,name=iPhone 17'git diff --checkNeed help on this PR? Tag
@codesmithwith what you need. Autofix is disabled.Summary by CodeRabbit