Skip to content

Rework the conversation reader around fixed actions - #421

Merged
rajzik merged 7 commits into
mainfrom
codex/issue-160-rework-conversation-reader
Aug 10, 2026
Merged

rajzik merged 7 commits into
mainfrom
codex/issue-160-rework-conversation-reader

Conversation

@gipity-bot

@gipity-bot gipity-bot Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Closes #160

Summary

  • keep exactly one conversation message expanded and bind the fixed reader toolbar to that active message
  • provide the compact Reply, applicable Reply All, Forward, Category, and More order while exposing thread-wide actions directly on regular-width layouts
  • replace single-category assignment with a searchable multi-select selector, including independently persisted custom-category creation and optimistic encrypted-sync presentation
  • cover active-message selection, adaptive toolbar ordering, category filtering and membership persistence, and optimistic category updates

Validation

  • pnpm install --frozen-lockfile — passed
  • pnpm lint — passed
  • pnpm format — passed
  • pnpm turbo run check-types — passed
  • pnpm test — passed
  • pnpm fallow — passed
  • mise exec -- zsh scripts/check-apple-lint.zsh — passed with zero violations
  • focused iPhone 17 simulator tests for reader expansion, toolbar ordering, category selection, multi-category persistence, and optimistic presentation — 5 passed
  • Debug iPhone 17 simulator suite — ran 1,737 tests; the existing testEditingOutboxReplyOnSameConnectionPreservesProviderReplyMetadata(requestsReadReceipt:) failed deterministically for the true argument at MailboxConnectionAdapterTests.swift:6133 because replacement.message.requestsReadReceipt was false; the same failure reproduced in isolation and is outside this issue's reader/category scope
  • mixed-connection scenario — passed
  • Release cached-presentation performance fixture — passed

The build also reports existing Swift 6 concurrency warnings in untouched product and test code.

Human actions

  • investigate or resolve the unrelated deterministic outbox read-receipt test failure before merge

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

@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary

  • Reworked the Apple conversation reader with newest-first ordering, one expanded message, and a fixed responsive toolbar.
  • Added adaptive Reply, Reply All, Forward, Category, More, pin, archive, delete, and thread-level actions.
  • Added searchable multi-select message categories with validation, cancellation, independent custom-category creation, and optimistic local updates before encrypted sync.
  • Added bulk category APIs across Gmail metadata services and mailbox connection adapters. Category metadata now preserves all assigned category IDs.
  • Added tests for action targeting, expansion stability, category selection, filtering, offline updates, toolbar ordering, and provider category-count guards.
  • No migration or configuration changes were introduced.
  • No TypeScript areas were affected.
  • Validation passed for installation, linting, formatting, type checks, tests, Fallow, Apple lint, focused simulator tests, mixed-connection scenarios, and release performance validation. One unrelated deterministic outbox read-receipt test failed in the Debug iPhone 17 suite. Existing Swift 6 concurrency warnings remain in untouched code.

Walkthrough

The conversation reader now uses a responsive contextual toolbar and exclusive message expansion. Category selection supports multiple searchable custom categories, creation, optimistic updates, cancellation, and bulk synchronization through mailbox metadata services.

Changes

Conversation reader and categories

Layer / File(s) Summary
Category models and selection
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift, apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
Custom categories now support collections, metadata editing, enabled-category filtering, searchable multi-selection, and creation.
Bulk category synchronization
apps/unwired-mail/unwired-mail/Services/Gmail/..., apps/unwired-mail/unwired-mail/Services/Mailbox/..., apps/unwired-mail/unwired-mailTests/*
Mailbox and Gmail metadata APIs normalize, route, persist, and synchronize multiple category IDs. Test services cover adapter validation, delayed updates, failures, and rollback.
Contextual conversation reader actions
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift, apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift, .changeset/rework-conversation-reader.md
The reader adds responsive toolbar actions, exclusive message expansion, contextual reply targeting, and optimistic category application. Tests cover expansion, toolbar ordering, rollback, cancellation, and body-loading states.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MailShellConversationReader
  participant GmailInboxViewModel
  participant MailboxConnectionRouter
  participant GmailMailboxConnectionAdapter
  participant GmailMessageMetadataService
  MailShellConversationReader->>GmailInboxViewModel: Apply selected category IDs
  GmailInboxViewModel->>MailboxConnectionRouter: setCategories(categoryIds, message)
  MailboxConnectionRouter->>GmailMailboxConnectionAdapter: Route by connection ID
  GmailMailboxConnectionAdapter->>GmailMessageMetadataService: setCategories(categoryIds, message)
  GmailMessageMetadataService-->>GmailMailboxConnectionAdapter: Return categorized metadata
  GmailMailboxConnectionAdapter-->>GmailInboxViewModel: Return updated metadata
  GmailInboxViewModel-->>MailShellConversationReader: Replace message category memberships
Loading

Possibly related issues

  • unwired-dev/product#156 — Covers the multi-category foundation and fixed conversation-reader actions implemented by this PR.

Possibly related PRs

Suggested reviewers: rajzik

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.03% 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
Title check ✅ Passed The title clearly summarizes the primary conversation-reader and fixed-toolbar changes.
Description check ✅ Passed The description directly covers the reader, toolbar, category, testing, and validation changes.
Linked Issues check ✅ Passed The changes address the linked issue's reader, toolbar, message targeting, multiselect category, persistence, and testing objectives.
Out of Scope Changes check ✅ Passed The adapter, metadata, UI, changeset, and test updates support the linked reader and multi-category objectives.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-160-rework-conversation-reader

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

@gipity-bot gipity-bot Bot added codex codex-automation human needed Human intervention is required before merge or issue completion labels Aug 10, 2026
@rajzik
rajzik marked this pull request as ready for review August 10, 2026 05:40
@github-actions

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 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: 13

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/GmailMessageMetadataService.swift (1)

2274-2288: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use exclusive locking for category writes. GmailMailboxConnectionAdapter.setCategories uses withSharedLock, while GmailMessageMetadataService.setCategories performs a full-list read-modify-write. Concurrent category assignments can load the same list and overwrite each other. Use withLock or an atomic store operation.

🤖 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/GmailMessageMetadataService.swift`
around lines 2274 - 2288, The category update flow in
GmailMessageMetadataService.setCategories must use exclusive locking for its
full-list read-modify-write operation. Update the underlying
GmailMailboxConnectionAdapter.setCategories path from withSharedLock to
withLock, or use an equivalent atomic store operation, while preserving the
existing category update behavior.
🤖 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 9076-9099: Update CustomCategoryEditorViewModel.create to use the
same reentrancy guard as setCategories before setting the shared isSaving flag,
so concurrent create/save operations cannot clear it prematurely. Preserve the
existing saveCategory, error handling, and defer-based reset behavior for the
guarded operation.
- Around line 8654-8670: Update replaceCategoryMemberships to preserve the
current MailboxMessageMetadata record and restore only its category identifiers
from the supplied message, rather than replacing the entire record. Ensure the
cancellation and failure calls using the pre-await message retain unrelated
in-flight changes such as read status while rolling back category membership.
- Around line 9816-9836: Update the category selector’s apply flow around
apply(selectedCategoryIds) to return or otherwise propagate the failure reason
from applyCategories instead of only a Boolean. Add sheet-local
applyErrorMessage state, set it when applying fails while preserving the
existing isApplying reset behavior, and attach an alert presenting that message
to the sheet’s inner NavigationStack so it appears above the presented sheet.
Add a focused test covering encrypted-sync failure and asserting the selector
displays the error.
- Around line 4448-4475: Update MessageCategorySelector to store a
MessageCategorySelection instance in state instead of its separate
selectedCategoryIds state. Replace the inline insert/remove logic with calls to
MessageCategorySelection.toggle(_:), and update reads and bindings to use the
model’s selectedCategoryIds so the UI routes selection changes through the
tested model.
- Around line 4845-4854: Update the .forward Button disabled condition to remove
the hasLoadedMessageBodyText(for:) cache requirement and gate only on
readerMutationIsDisabled and inboxViewModel.isLoadingMessageBody, allowing
prepareForward to fetch missing or evicted text. Add a focused test covering an
expanded message with evicted body text and assert that Forward remains enabled.

In `@apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift`:
- Around line 865-871: Update MailboxMessageMetadata.assigningCategories and
GmailMessageMetadata.assigningCategories to use one shared category-ID
normalization helper, preserving empty strings while uniquing and sorting the
IDs. Remove the mailbox-specific filtering and ensure both implementations share
the same contract and normalization path.
- Around line 1372-1381: Remove the single-category default implementation from
MailboxMetadataSyncing.setCategories in
apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift:1372-1381
so unsupported requests cannot report success without persistence. Apply the
same change to GmailMessageMetadataSyncing in
apps/unwired-mail/unwired-mail/Services/Gmail/GmailMessageMetadataService.swift:347-355.
In
apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift:2286-2293,
explicitly implement setCategories for IMAPMailboxConnectionAdapter,
MicrosoftGraphMailboxConnectionAdapter, and EWSMailboxConnectionAdapter,
supporting all category IDs or throwing
MailboxConnectionAdapterError.unsupportedProvider.
- Around line 2911-2931: Collapse the duplicate logic in
MailboxConnection.overrideCategory into setCategories, making overrideCategory
delegate through setCategories with the appropriate category input. Preserve
setCategories’ shared-lock acquisition, active-connection check, metadata
delegation, and connectionRemoved cleanup as the single implementation.

In `@apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift`:
- Around line 9236-9245: Add a focused removal-cleanup test for
GmailMailboxConnectionAdapter.setCategories, modeled on
testGmailMailboxRemovalWaitsForInFlightCategoryOverride: start an in-flight
category update through DelayedAdapterProviderReadService, remove the
connection, release the gate, and assert the event log is ["categories-set",
"local-state-cleared"].
- Around line 5718-5741: Add reduced-capability coverage to
testConversationReaderToolbarKeepsAdaptiveActionOrder by invoking
MailShellReaderToolbarLayout.actions with canReplyAll false and an empty
providerActions list, while retaining the other capabilities enabled. Assert
compact returns [.reply, .forward, .category, .more] and regular returns
[.reply, .forward, .category, .pin, .more].
- Around line 1662-1709: Add focused tests alongside
testCategoryApplyUpdatesReaderMetadataBeforeEncryptedSyncCompletes for
GmailInboxViewModel.setCategories failure and cancellation paths. Make the
metadata service throw in one test, then assert the thread’s messageCategoryIds
revert to their original values and categoryOverrideErrorMessage is set; cancel
the applyTask in a second test and assert the categories revert without setting
an error message. Reuse the existing adapter and delayed-service setup patterns.
- Around line 1652-1659: Strengthen the test around assigningCategories by
passing an unsorted category list containing a duplicate, then assert that both
metadataService.setCategoryIds and updated.messageCategoryIds contain the
deduplicated, sorted category IDs. Preserve the existing adapter.setCategories
call and session setup while ensuring the assertions verify normalization.
- Around line 5706-5716: Add an assertion in the existing toggleMessageExpansion
test confirming the latest message remains expanded after attempting to toggle
it while expanded. Preserve the current assertions for olderMessage and use
isMessageExpanded(latestMessage, in: thread) to pin the non-collapsible
behavior.

---

Outside diff comments:
In
`@apps/unwired-mail/unwired-mail/Services/Gmail/GmailMessageMetadataService.swift`:
- Around line 2274-2288: The category update flow in
GmailMessageMetadataService.setCategories must use exclusive locking for its
full-list read-modify-write operation. Update the underlying
GmailMailboxConnectionAdapter.setCategories path from withSharedLock to
withLock, or use an equivalent atomic store operation, while preserving the
existing category update behavior.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6e81c838-b3b4-460b-b1b5-a977b95bae06

📥 Commits

Reviewing files that changed from the base of the PR and between ce74bb2 and 2c9c5d6.

📒 Files selected for processing (6)
  • .changeset/rework-conversation-reader.md
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/GmailMessageMetadataService.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Apple performance · Unwired Mail
🧰 Additional context used
📓 Path-based instructions (4)
.changeset/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Create a Changeset for runtime behavior changes or exported type/API changes; tests-only, internal refactors, documentation-only changes, and JSDoc-only maintenance may omit one at maintainer discretion.

Files:

  • .changeset/rework-conversation-reader.md
apps/unwired-mail/**/*.{swift,m,mm}

📄 CodeRabbit inference engine (AGENTS.md)

The Apple app must provide formatter, linter, and test commands. Run zsh scripts/check-apple-lint.zsh for format and lint, and use the documented xcodebuild test command for tests; SwiftLint runs in strict mode.

Files:

  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/GmailMessageMetadataService.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
apps/unwired-mail/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

Apple validation must use SwiftLint strict mode and Swift Format strict linting; if Apple tooling is unavailable, report that clearly in the final handoff.

Files:

  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/GmailMessageMetadataService.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift

⚙️ CodeRabbit configuration file

apps/unwired-mail/**/*.swift: Prioritize functional correctness, data protection, and Apple platform
behavior over style already enforced by swift-format and SwiftLint.
Check actor isolation, cancellation, lifecycle transitions, persistence,
retry and offline behavior, and Keychain accessibility. Flag credentials,
tokens, mail content, or decrypted Product Sync data that can be logged,
persisted outside approved device-local storage, or cross a documented
privacy boundary. Require focused tests for changed behavior and failure
paths.

Files:

  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/GmailMessageMetadataService.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
**/*Tests/**/*.swift

⚙️ CodeRabbit configuration file

**/*Tests/**/*.swift: Verify externally observable behavior, privacy invariants, error paths,
cancellation or restart behavior where relevant, and deterministic
isolation. Flag fragile sleeps, ordering dependencies, and tests that
duplicate the implementation instead of exercising it.

Files:

  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
🪛 markdownlint-cli2 (0.23.2)
.changeset/rework-conversation-reader.md

[warning] 4-4: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🔇 Additional comments (19)
.changeset/rework-conversation-reader.md (1)

1-4: LGTM!

apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift (13)

1497-1499: LGTM!

Also applies to: 1687-1688, 2177-2179


1541-1546: LGTM!


2937-2951: LGTM!

Also applies to: 3021-3041


3203-3211: LGTM!


4408-4446: LGTM!


4500-4507: LGTM!

Also applies to: 4665-4674, 4696-4697


4770-4844: LGTM!

Also applies to: 4855-4899


4901-5002: LGTM!


8631-8653: LGTM!


8969-9075: LGTM!

Also applies to: 9100-9174, 9207-9213


9680-9696: LGTM!

Also applies to: 9751-9815, 9837-9916


5004-5028: 🗄️ Data Integrity & Integration

No reachable concurrent assignment exists. The reader sheet is the only production caller of GmailInboxViewModel.setCategories, and MessageCategorySelector disables Apply while the request runs.

			> Likely an incorrect or invalid review comment.

9084-9084: 🗄️ Data Integrity & Integration

Keep the UUID identifier. Category IDs are opaque strings. Persistence, notification rules, and search support the UUID without a custom: prefix.

			> Likely an incorrect or invalid review comment.
apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift (1)

843-844: LGTM!

apps/unwired-mail/unwired-mail/Services/Gmail/GmailMessageMetadataService.swift (2)

331-336: LGTM!


2289-2314: LGTM!

apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift (2)

5743-5761: LGTM!


8989-8989: LGTM!

Also applies to: 9117-9124

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
Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
Comment thread apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift

@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)

4665-4674: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix: stale reader-level alert reappears after cancelling the category sheet.

MessageCategorySelector now shows its own alert for applyErrorMessage (Line 9851-9855), and the user dismisses it with "OK." applyCategories (Line 5013-5037) also sets readerErrorMessage/readerErrorSource = .categoryOverride on the same failure, on MailShellConversationReader.

applyCategories clears readerErrorMessage/readerErrorSource only on a later successful apply (Line 5031-5035). If the user dismisses the sheet's own error alert and then taps "Cancel" instead of retrying, categorySelection becomes nil and the sheet closes, but readerErrorMessage is still set. MailShellConversationReader's own .alert("Message action failed", ...) (Line 4675-4695) then becomes visible, re-showing the same error the user already acknowledged inside the sheet, under a different title.

Clear the categoryOverride error state when the sheet dismisses, regardless of how it dismisses.

🐛 Proposed fix to clear stale reader-level error on sheet dismissal
-    .sheet(item: $categorySelection) { selection in
+    .sheet(item: $categorySelection, onDismiss: {
+      if readerErrorSource == .categoryOverride {
+        readerErrorConnectionId = nil
+        readerErrorMessage = nil
+        readerErrorSource = nil
+      }
+    }) { selection in
       MessageCategorySelector(
         categoryChoices: categoryChoices,
         createCustomCategory: createCustomCategory,
         selection: selection,
         apply: { categoryIds in
           await applyCategories(categoryIds, to: selection.message)
         }
       )
     }

Add a focused test that fails the encrypted sync, dismisses the sheet's own alert, cancels the sheet, and asserts readerErrorMessage no longer holds the acknowledged category-override error.

As per path instructions: "Prioritize functional correctness, data protection, and Apple platform behavior over style already enforced by swift-format and SwiftLint" and "Require focused tests for changed behavior and failure paths."

Also applies to: 5013-5037, 9828-9863

🤖 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 4665 - 4674, Clear MailShellConversationReader’s readerErrorMessage and
readerErrorSource when the categorySelection sheet dismisses, but only for the
.categoryOverride source, so cancelling after acknowledging
MessageCategorySelector’s applyErrorMessage cannot re-show the stale alert.
Update the dismissal handling around categorySelection and add a focused test
covering encrypted-sync failure, dismissing the selector alert, cancelling the
sheet, and verifying readerErrorMessage is cleared.

Source: Path instructions

🤖 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/IMAPMailboxConnectionAdapter.swift`:
- Around line 1810-1820: Add focused failure-path tests for setCategories in
apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift:1810-1820,
apps/unwired-mail/unwired-mail/Services/Mailbox/EWSMailboxConnectionAdapter.swift:3569-3579,
and
apps/unwired-mail/unwired-mail/Services/Mailbox/MicrosoftGraphMailboxConnectionAdapter.swift:3202-3212,
using each adapter’s corresponding test suite. For each provider, call
setCategories with zero and multiple category IDs, assert
MailboxConnectionAdapterError.unsupportedProvider is thrown, and verify stored
message metadata remains unchanged.

---

Outside diff comments:
In `@apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift`:
- Around line 4665-4674: Clear MailShellConversationReader’s readerErrorMessage
and readerErrorSource when the categorySelection sheet dismisses, but only for
the .categoryOverride source, so cancelling after acknowledging
MessageCategorySelector’s applyErrorMessage cannot re-show the stale alert.
Update the dismissal handling around categorySelection and add a focused test
covering encrypted-sync failure, dismissing the selector alert, cancelling the
sheet, and verifying readerErrorMessage is cleared.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7dc70c7a-179a-4c1d-ab91-dda7c205c128

📥 Commits

Reviewing files that changed from the base of the PR and between 2c9c5d6 and a335423.

📒 Files selected for processing (12)
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/GmailMessageMetadataService.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/MessageCategorizationService.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/EWSMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MicrosoftGraphMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
  • apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mailTests/PinSyncServiceTests.swift
💤 Files with no reviewable changes (1)
  • apps/unwired-mail/unwired-mail/Services/Gmail/GmailMessageMetadataService.swift
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Apple Debug · Unwired Mail
  • GitHub Check: Apple performance · Unwired Mail
🧰 Additional context used
📓 Path-based instructions (3)
apps/unwired-mail/**/*.{swift,m,mm}

📄 CodeRabbit inference engine (AGENTS.md)

The Apple app must provide formatter, linter, and test commands. Run zsh scripts/check-apple-lint.zsh for format and lint, and use the documented xcodebuild test command for tests; SwiftLint runs in strict mode.

Files:

  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MicrosoftGraphMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/MessageCategorizationService.swift
  • apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/EWSMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/PinSyncServiceTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
apps/unwired-mail/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

Apple validation must use SwiftLint strict mode and Swift Format strict linting; if Apple tooling is unavailable, report that clearly in the final handoff.

Files:

  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MicrosoftGraphMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/MessageCategorizationService.swift
  • apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/EWSMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/PinSyncServiceTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift

⚙️ CodeRabbit configuration file

apps/unwired-mail/**/*.swift: Prioritize functional correctness, data protection, and Apple platform
behavior over style already enforced by swift-format and SwiftLint.
Check actor isolation, cancellation, lifecycle transitions, persistence,
retry and offline behavior, and Keychain accessibility. Flag credentials,
tokens, mail content, or decrypted Product Sync data that can be logged,
persisted outside approved device-local storage, or cross a documented
privacy boundary. Require focused tests for changed behavior and failure
paths.

Files:

  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MicrosoftGraphMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Gmail/MessageCategorizationService.swift
  • apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/EWSMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/PinSyncServiceTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
**/*Tests/**/*.swift

⚙️ CodeRabbit configuration file

**/*Tests/**/*.swift: Verify externally observable behavior, privacy invariants, error paths,
cancellation or restart behavior where relevant, and deterministic
isolation. Flag fragile sleeps, ordering dependencies, and tests that
duplicate the implementation instead of exercising it.

Files:

  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift
  • apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift
  • apps/unwired-mail/unwired-mailTests/PinSyncServiceTests.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
🔇 Additional comments (8)
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift (1)

4759-4765: LGTM!

Also applies to: 8640-8685, 9090-9114, 9766-9944

apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift (1)

1652-1663: LGTM!

Also applies to: 1714-1797, 3625-3671, 5537-5550, 5830-5915, 5917-5936, 9319-9424

apps/unwired-mail/unwired-mail/Services/Mailbox/MailboxConnection.swift (1)

843-903: LGTM!

Also applies to: 2884-2912

apps/unwired-mail/unwired-mail/Services/Gmail/MessageCategorizationService.swift (1)

1987-2013: LGTM!

apps/unwired-mail/unwired-mailTests/GmailMessageMetadataServiceTests.swift (1)

7053-7062: LGTM!

Also applies to: 7296-7309, 7568-7575, 7696-7703

apps/unwired-mail/unwired-mailTests/GmailPushRelayServiceTests.swift (1)

3807-3815: LGTM!

apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift (1)

1726-1733: LGTM!

apps/unwired-mail/unwired-mailTests/PinSyncServiceTests.swift (1)

1147-1154: LGTM!

@rajzik

rajzik commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

@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: d330dcebce

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

@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/Features/Account/AccountView.swift (1)

9800-9824: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Disable category editing while Apply is in progress.

Line 9839 passes the current selection to apply. The category rows and Add New Category remain enabled while that operation awaits. A user can change the displayed selection after the request starts, but the earlier selection is the one that syncs and the sheet then dismisses.

Disable the list content while isApplying is true. Add focused coverage for an apply operation that remains suspended.

Proposed fix
       .navigationTitle("Categories")
       .navigationBarTitleDisplayMode(.inline)
       .searchable(text: $query, prompt: "Search Categories")
+      .disabled(isApplying)
       .toolbar {

Also applies to: 9835-9856

🤖 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 9800 - 9824, Update the category list content containing the
`ForEach(filteredChoices)` rows and `Add New Category` button to be disabled
while `isApplying` is true, preventing selection changes or category creation
during `apply`. Add focused coverage for an apply operation that remains
suspended and verify the list controls stay disabled until it completes.

Sources: Coding guidelines, Path instructions

🤖 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 9800-9824: Update the category list content containing the
`ForEach(filteredChoices)` rows and `Add New Category` button to be disabled
while `isApplying` is true, preventing selection changes or category creation
during `apply`. Add focused coverage for an apply operation that remains
suspended and verify the list controls stay disabled until it completes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: df9f6849-8162-4ff9-995e-5639e0f2fce1

📥 Commits

Reviewing files that changed from the base of the PR and between df8bf93 and bcc0b2f.

📒 Files selected for processing (4)
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Apple Debug · Unwired Mail
  • GitHub Check: Apple performance · Unwired Mail
🧰 Additional context used
📓 Path-based instructions (3)
apps/unwired-mail/**/*.{swift,m,mm}

📄 CodeRabbit inference engine (AGENTS.md)

The Apple app must provide formatter, linter, and test commands. Run zsh scripts/check-apple-lint.zsh for format and lint, and use the documented xcodebuild test command for tests; SwiftLint runs in strict mode.

Files:

  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
apps/unwired-mail/**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

Apple validation must use SwiftLint strict mode and Swift Format strict linting; if Apple tooling is unavailable, report that clearly in the final handoff.

Files:

  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift

⚙️ CodeRabbit configuration file

apps/unwired-mail/**/*.swift: Prioritize functional correctness, data protection, and Apple platform
behavior over style already enforced by swift-format and SwiftLint.
Check actor isolation, cancellation, lifecycle transitions, persistence,
retry and offline behavior, and Keychain accessibility. Flag credentials,
tokens, mail content, or decrypted Product Sync data that can be logged,
persisted outside approved device-local storage, or cross a documented
privacy boundary. Require focused tests for changed behavior and failure
paths.

Files:

  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift
**/*Tests/**/*.swift

⚙️ CodeRabbit configuration file

**/*Tests/**/*.swift: Verify externally observable behavior, privacy invariants, error paths,
cancellation or restart behavior where relevant, and deterministic
isolation. Flag fragile sleeps, ordering dependencies, and tests that
duplicate the implementation instead of exercising it.

Files:

  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
🔇 Additional comments (2)
apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift (2)

9770-9791: LGTM!

Also applies to: 9857-9893


4467-4469: 📐 Maintainability & Code Quality

Run the required Apple validation.

Apple tooling and validation output were not available in this review context. Run zsh scripts/check-apple-lint.zsh, then run the repository’s documented xcodebuild test command after the compile fix.

  • apps/unwired-mail/unwired-mail/Features/Account/AccountView.swift#L4467-L4469: validate the Swift collection API fix with strict formatting and linting.
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift#L1437-L1464: validate the adapter categorization integration.
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift#L442-L464: run the focused IMAP categorization tests.
  • apps/unwired-mail/unwired-mailTests/MailboxConnectionAdapterTests.swift#L1631-L1663: run the focused category-assignment and rollback tests.

Source: Coding guidelines

@rajzik
rajzik merged commit f1cedf7 into main Aug 10, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex codex-automation human needed Human intervention is required before merge or issue completion

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rework the conversation reader around newest-first Threads and fixed actions

1 participant