Skip to content

Keep every Gmail connection fresh - #137

Merged
rajzik merged 10 commits into
mainfrom
codex/issue-59-keep-gmail-connections-fresh
Jul 24, 2026
Merged

rajzik merged 10 commits into
mainfrom
codex/issue-59-keep-gmail-connections-fresh

Conversation

@rajzik

@rajzik rajzik commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Closes #59

Summary

  • coordinate launch, foreground, five-minute active polling, manual refresh, push, and historical backfill across every authorized Gmail connection
  • show durable per-connection freshness states for authorization, syncing, pending history, offline recovery, errors, and last successful sync
  • serialize connection-scoped metadata work, preempt lower-priority backfills safely, and keep cached mail available while history continues
  • reconcile push wakeups even when notification rules are unavailable, then publish successful freshness updates
  • document the freshness behavior and add a release changeset

Validation

  • mise exec -- pnpm install --frozen-lockfile
  • mise exec -- pnpm lint
  • mise exec -- pnpm format
  • mise exec -- pnpm turbo run check-types
  • mise exec -- pnpm test
  • mise exec -- pnpm fallow
  • mise exec -- zsh scripts/check-apple-lint.zsh
  • xcodebuild test -project apps/unwired-mail/unwired-mail.xcodeproj -scheme unwired-mail -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:unwired-mailTests/GmailMessageMetadataServiceTests
  • xcodebuild test -project apps/unwired-mail/unwired-mail.xcodeproj -scheme unwired-mail -destination 'platform=iOS Simulator,name=iPhone 17'
  • git diff --check

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features
    • Added “mailbox freshness” synchronization coordination across launch, foreground, background wakeups, active polling, historical backfill, and manual refresh.
    • Added per-connection sync status in the UI, with persisted “last successful sync” tracking.
  • Bug Fixes
    • Improved background push relay reconciliation with clearer sync-phase updates, safer scheduling of generic fallback, and reduced sync overlap via per-connection serialization.
    • Improved recovery behavior so failed/offline syncs don’t incorrectly persist “successful” state.
  • Documentation
    • Updated README to clarify watch renewal, reconciliation, and fallback behavior after background wakeups.
  • Tests
    • Expanded test coverage for freshness coordination, sync gating, push wakeup behavior, persistence, cancellation, and notification-driven reloads.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@rajzik, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 seconds

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

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 configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f06f63eb-8b4b-4b58-a5f7-71e906aa767d

📥 Commits

Reviewing files that changed from the base of the PR and between 66844e6 and 7068b8a.

📒 Files selected for processing (4)
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
  • apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds 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.

Changes

Gmail mailbox freshness

Layer / File(s) Summary
Freshness coordinator and account integration
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift, .changeset/..., README.md
Adds persisted synchronization state, lifecycle orchestration, active polling, manual refresh UI, per-connection statuses, cleanup, retry handling, and freshness documentation.
Inbox synchronization flow integration
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
Routes inbox sync and historical backfill through the freshness coordinator and distinguishes synchronizing loads from navigation-only loads.
Per-connection adapter synchronization gate
apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
Serializes backfill and inbox metadata operations for each mailbox connection.
Push wakeup status and persistence
apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift, apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift
Publishes sync phases, records successful sync timestamps, and handles background processing outcomes and fallback delivery.
Freshness behavior validation
apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
Tests launch and foreground synchronization, polling, persistence, cancellation, backfill behavior, session changes, and per-connection serialization.

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
Loading

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures the main feature: keeping every Gmail connection fresh.
Linked Issues check ✅ Passed The code and tests align with the issue by adding launch/foreground sync, active polling, push reconciliation, per-connection status, and manual refresh support.
Out of Scope Changes check ✅ Passed The changes stay focused on Gmail connection freshness, related tests, docs, and release notes without unrelated feature work.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-59-keep-gmail-connections-fresh

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

rajzik commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift Outdated
Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift Outdated
@rajzik

rajzik commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@rajzik

rajzik commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 17d8b0201f

ℹ️ 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".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7a6e5cc and 17d8b02.

📒 Files selected for processing (7)
  • .changeset/gmail-mailbox-freshness.md
  • README.md
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
  • apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift

Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
Comment thread apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift Outdated
Comment thread apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift Outdated
Comment thread apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift Outdated
Comment thread apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift Outdated
Comment thread apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift Outdated
@rajzik
rajzik marked this pull request as ready for review July 23, 2026 21:10
@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

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.

@rajzik

rajzik commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@rajzik

rajzik commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Do not retry canceled loads.

The catch at Line 3441 also handles cancellation, so a canceled AccountView task can issue a second refreshConnections() request and publish a spurious cancellation error. Since completeLoadingConnections() invokes a helper that swallows cancellation, check cancellation before retrying and after completion without updating errorMessage.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 17d8b02 and 552a422.

📒 Files selected for processing (4)
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift

Comment thread apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift Outdated
Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift Outdated
Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
@rajzik

rajzik commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@rajzik

rajzik commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Seed 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 win

Make the gate-contention check deterministic. pollAttempted only proves the task started, not that it reached MailboxConnectionSyncGate.acquire. Replace the Task.yield() loop with a test hook that signals when the poll is actually queued, then assert pollAcquired is still false before release.

🤖 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 win

Concurrent pin toggles on different messages can misattribute errors.

togglePin (PinViewModel, lines 2374-2396) permits concurrent toggles across different messageIds — only same-message reentry is guarded via updatingMessageIds. The caller at lines 1982-1989 awaits its own toggle, then reads the shared pinViewModel.errorMessage to decide whether to show readerErrorMessage. If message A's toggle succeeds while a concurrently-started toggle for message B fails, A's completion sees B's leftover errorMessage and incorrectly shows a "Message action failed" alert for a successful action.

Have togglePin return 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 win

Cancellation is swallowed instead of stopping the prefetch loop.

The catch-all at line 3281-3283 also catches CancellationError, so cancelling bodyPrefetchTask (e.g. a newer prefetch superseding it) doesn't stop iteration — the loop continues to the next connection. This is inconsistent with the catch 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

📥 Commits

Reviewing files that changed from the base of the PR and between 552a422 and bf7a41f.

📒 Files selected for processing (3)
  • README.md
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: a34d270577

ℹ️ 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".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Clear stale authorization-required status after reauthorization.

updateConnections() preserves the existing status for IDs that remain present. When a connection changes from .required to .authorized, status(for:) returns that stale .authorizationRequired phase until a later sync rewrites it. This is especially visible when the connection cannot synchronize immediately.

Reset the phase to .idle on 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 win

Do 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 priorStatus and overwrite that newer lastSuccessfulSyncAt in memory. Because status(for:) prefers statuses over 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 win

Guard sync result handling by syncId.

After updateConnections() or cancelAll() removes an in-flight entry, the canceled task can still resume here and write .idle or .failure. If a replacement sync starts first, the old task can overwrite its .syncing or successful state.

Require inFlightSyncs[connection.id]?.id == syncId plus 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 win

Defer backfill until after sync completes loadAfterConnectionChange(..., synchronizes: true) currently starts historical backfill before sync(), so the sync path cancels and may restart the same work. Pass startsBackfill: false here and let sync() 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

📥 Commits

Reviewing files that changed from the base of the PR and between bf7a41f and a34d270.

📒 Files selected for processing (3)
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift

@rajzik

rajzik commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@rajzik

rajzik commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift Outdated
return
}
guard !Task.isCancelled, isSessionCurrent(session) else { return }
await synchronize(connections: connections())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rajzik

rajzik commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@rajzik

rajzik commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Revalidate the route before recording sync success.

A session/connection can change after syncRecentInbox accepts shouldPersist but before it returns. The guard at Line 1382 prevents watermark advancement, but Lines 1360-1381 already persist and publish a stale successful sync. Guard currentWatchForRoute() before saving successfulSyncAt and 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

📥 Commits

Reviewing files that changed from the base of the PR and between a34d270 and be56f61.

📒 Files selected for processing (3)
  • apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
  • apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: be56f61678

ℹ️ 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".

@rajzik

rajzik commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@rajzik

rajzik commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Assert polling coverage per connection.

A total count equal to fixture.connections.count still 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 win

Track the cancelled and next waiters separately.

Because both waiters write to the same pollAcquired boolean, 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

📥 Commits

Reviewing files that changed from the base of the PR and between be56f61 and 66844e6.

📒 Files selected for processing (2)
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift

Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift Outdated
Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift Outdated
Comment thread apps/unwired-mail/unwired-mail/Services/Gmail/GmailPushRelayService.swift Outdated
@rajzik

rajzik commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@rajzik

rajzik commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 7068b8a3e4

ℹ️ 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".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Keep every Gmail connection fresh

1 participant