Skip to content

Add Attachment Preview to standards-based mail - #431

Closed
gipity-bot[bot] wants to merge 6 commits into
mainfrom
codex/issue-323-add-attachment-preview
Closed

gipity-bot[bot] wants to merge 6 commits into
mainfrom
codex/issue-323-add-attachment-preview

Conversation

@gipity-bot

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

Copy link
Copy Markdown
Contributor

Summary

  • expose ordinary Standards-Based IMAP attachment descriptors from UID-validity-checked BODYSTRUCTURE responses while excluding readable body parts and inline Content-ID images
  • download only the selected numeric MIME section through a bounded partial fetch, transfer-decode it, and reuse the shared protected attachment store and policy gate
  • persist attachment descriptors (but never attachment bytes) in the encrypted message-body cache, with refresh behavior for legacy and prefetch-only cache entries
  • document the provider boundary and add a patch changeset

Validation

  • mise exec -- pnpm lint — passed
  • mise exec -- pnpm format — passed
  • mise exec -- pnpm turbo run check-types — passed
  • mise exec -- pnpm test — passed
  • mise exec -- pnpm fallow — passed
  • mise exec -- zsh scripts/check-apple-lint.zsh — passed (121 Swift files, 0 violations)
  • focused IMAPMailboxConnectionAdapterTests — passed (37 tests)
  • Debug Apple CI-parity suite — passed (1,745 tests in 41 suites; one intentionally recorded known issue)
  • isolated mixed-connection scenario — passed
  • Release-only cached-presentation performance fixture — passed

Human actions

None beyond normal review.

Closes #323


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

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 37 minutes

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

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4abc5137-a441-41e8-bd7c-9088a1cadd3e

📥 Commits

Reviewing files that changed from the base of the PR and between c3e7933 and 8cb48e2.

📒 Files selected for processing (2)
  • apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.swift
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
📝 Walkthrough

Summary

  • Added standards-based IMAP attachment previews through the shared Attachment Preview UI.
  • Excluded readable MIME parts and inline Content-ID images from attachment descriptors.
  • Added bounded partial fetches, transfer decoding, cancellation handling, and size validation before protected storage.
  • Stored attachment descriptors in the encrypted message-body cache without attachment bytes.
  • Preserved legacy cache compatibility and refreshed entries without attachment metadata.
  • Preserved existing policy, offline, retry, protected-store, Quick Look, and Share/Open behavior.
  • Updated the IMAP security boundary ADR and added a patch changeset.

Affected areas

  • Apple / Swift: IMAPMailboxConnectionAdapter, SystemIMAPMailboxClient, MIME parsing, encrypted body caching, protected attachment downloads, and IMAP tests.
  • TypeScript: No TypeScript files changed. The shared Attachment Preview UI consumes the new mailbox attachment path.

Scope

  • Microsoft Graph and Exchange Web Services have no attachment request path in this change.
  • SMTP sending and Draft Assets remain unchanged.

Validation

  • IMAP tests cover cache behavior, descriptor persistence, attachment filtering, partial downloads, oversized literals, declared-size rejection, and cancellation.
  • Listed validation checks and test suites passed.

Walkthrough

Changes

IMAP attachment preview

Layer / File(s) Summary
IMAP attachment parsing and bounded downloads
apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.swift
IMAP body structures expose ordinary attachment descriptors, exclude inline and readable parts, decode attachment data, and enforce declared, decoded, and literal size limits.
Structured body cache and adapter orchestration
apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
Structured bodies and versioned encrypted cache payloads preserve attachment metadata. Body loading supports legacy cache entries, provider fallback, authorization, sync gating, and attachment retrieval.
Attachment flow validation
apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
Tests cover descriptor persistence, cache refresh, downloads, cancellation, inline filtering, request tracking, fixtures, and size rejection.
Release and provider boundary documentation
.changeset/preview-imap-attachments.md, docs/adr/0030-gate-incoming-attachment-downloads.md
The changeset records preview support. ADR 0030 documents IMAP attachment boundaries and unsupported provider paths.

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

Sequence Diagram(s)

sequenceDiagram
  participant MailboxClient
  participant IMAPMailboxConnectionAdapter
  participant IMAPMessageBodyService
  participant SystemIMAPMailboxClient
  participant SwiftMail
  MailboxClient->>IMAPMailboxConnectionAdapter: request message attachment
  IMAPMailboxConnectionAdapter->>IMAPMessageBodyService: authorize and gate download
  IMAPMessageBodyService->>SystemIMAPMailboxClient: fetch matching MIME section
  SystemIMAPMailboxClient->>SwiftMail: retrieve and decode attachment
  SwiftMail-->>SystemIMAPMailboxClient: return attachment bytes
  SystemIMAPMailboxClient-->>IMAPMessageBodyService: return validated data
  IMAPMessageBodyService-->>IMAPMailboxConnectionAdapter: return attachment data
  IMAPMailboxConnectionAdapter-->>MailboxClient: provide attachment data
Loading

Possibly related issues

  • unwired-dev/product#321 — Covers the standards-based IMAP attachment descriptor, protected download, and cache integration implemented by this PR.

Possibly related PRs

  • unwired-dev/product#425 — Introduces the experimental SwiftMail IMAP integration used by this PR.
  • unwired-dev/product#426 — Provides the SwiftMail engine used for IMAP body and attachment loading.
  • unwired-dev/product#142 — Provides the IMAP mailbox adapter and system client extended with structured body caching and attachment downloads.

Suggested reviewers: rajzik

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: Attachment Preview support for standards-based mail.
Description check ✅ Passed The description accurately covers IMAP attachment descriptors, bounded downloads, caching, validation, and the linked issue.
Linked Issues check ✅ Passed The changes satisfy the coding objectives in [#323], including safe IMAP filtering, bounded downloads, policy gating, protected storage, and cache refresh behavior.
Out of Scope Changes check ✅ Passed The changes remain within [#323] and support its implementation through focused code, tests, documentation, and a changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 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-323-add-attachment-preview

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

@rajzik
rajzik marked this pull request as ready for review August 10, 2026 19:59
@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: 8

🤖 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 @.changeset/preview-imap-attachments.md:
- Line 5: Update .changeset/preview-imap-attachments.md line 5 to use
“Attachment Preview,” “Standards-Based Mailbox Connections,” and protected
“Downloaded Attachment” storage. Update
docs/adr/0030-gate-incoming-attachment-downloads.md line 9 to use “Inline
Image,” “Bounded Encrypted Body Cache,” “Attachment Preview,” and “Downloaded
Attachment,” explicitly stating that downloaded bytes remain device-local.

In
`@apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift`:
- Around line 1445-1460: Update the cached-body decoding flow around
IMAPCachedMessageBodyPayload so a successfully decoded payload with a version
other than currentVersion is treated as unsupported and does not enter the
legacy UTF-8 fallback. Preserve the legacy branch only for data that cannot
decode as the payload type, and ensure the version mismatch propagates through
the existing catch/removal and refresh path.
- Around line 1288-1291: Update the catch block in the body-loading method to
rethrow CancellationError before checking the cached body, while preserving the
cache fallback for other provider errors. Keep cancellation behavior consistent
with prefetchMessageBodies.

In
`@apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.swift`:
- Around line 128-139: Update the fetch-bound calculation in the attachment
download flow around attachmentPart and maximumRequestedByteCount so a declared
byteCount of zero is resolved as the unknown-size case before constructing the
BODY.PEEK range. Ensure zero requests the full
MailboxMessageAttachmentPolicy.maximumByteCount allowance, while positive
declared sizes retain their existing byteCount + 1 limit.
- Around line 314-318: Update the response-reading loop around
trailingLiteralLength and response.append so it tracks the total accumulated
response size, including literals and other appended line data. Compare that
cumulative size against maximumLiteralByteCount and throw
MailboxMessageAttachmentError.invalidResponse as soon as the bound is exceeded,
while preserving the existing per-literal validation.
- Around line 895-905: Update the attachment parsing flow associated with
isAttachment so MailboxMessageAttachment.byteCount uses the decoded payload size
rather than the encoded BODYSTRUCTURE size in values[6]. Retain the encoded size
for BODY.PEEK read bounds, and add a BASE64 boundary test covering a decoded
payload at the maximum allowed byte count.

In `@apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift`:
- Around line 972-1034: Add coverage for the two untested paths: extend
RecordingIMAPClient with a beforeAttachmentReturn suspension hook and test that
cancelling loadMessageAttachment maps to CancellationError, then add a test that
runs prefetchMessageBodies before loadMessageBody for an attachment-bearing
message and verifies a second body request refreshes attachment descriptors.
Reuse the existing adapter, cache, and recording-client test helpers while
asserting the externally visible request and descriptor behavior.

In `@docs/adr/0030-gate-incoming-attachment-downloads.md`:
- Line 9: Update the ADR paragraph to explicitly identify
SystemIMAPMailboxClient and IMAPMailboxConnectionAdapter as the Apple Swift
owners of the provider-neutral attachment descriptor and download boundary, and
state that no TypeScript layer participates in this boundary.
🪄 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: 4bb8b741-2b6b-42fb-81a0-52e0670284f5

📥 Commits

Reviewing files that changed from the base of the PR and between 6da192c and a4d528c.

📒 Files selected for processing (5)
  • .changeset/preview-imap-attachments.md
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.swift
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • docs/adr/0030-gate-incoming-attachment-downloads.md
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Apple Debug · Unwired Mail
🧰 Additional context used
📓 Path-based instructions (6)
docs/adr/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Record durable architecture decisions and privacy-boundary decisions in docs/adr/.

Files:

  • docs/adr/0030-gate-incoming-attachment-downloads.md
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Treat docs as product truth, not marketing. Prefer precise domain language
from CONTEXT.md; flag undefined terms, synonym drift, and conflicts with
existing ADRs under docs/adr/. For ADR changes, require a clear decision,
consequences, and privacy or security boundary impact; flag silent
reversals of prior ADRs without an explicit supersession note. For
docs/agents/ and bootstrap docs, verify agent workflow, validation, and
triage guidance stay aligned with AGENTS.md. Flag broken cross-links,
speculative future claims presented as shipped behavior, and docs that
omit the Apple vs TypeScript ownership of a boundary they describe.

Files:

  • docs/adr/0030-gate-incoming-attachment-downloads.md
.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/preview-imap-attachments.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-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.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/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.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/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.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
🪛 markdownlint-cli2 (0.23.2)
.changeset/preview-imap-attachments.md

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

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

🔇 Additional comments (3)
apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.swift (1)

77-111: LGTM!

Also applies to: 861-863, 989-1014

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

194-223: LGTM!

Also applies to: 1198-1224, 1264-1273, 1304-1327, 2042-2064

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

1278-1337: LGTM!

Also applies to: 1339-1377, 1379-1417, 1711-1743

Comment thread .changeset/preview-imap-attachments.md Outdated
Comment thread apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.swift Outdated
Comment thread docs/adr/0030-gate-incoming-attachment-downloads.md Outdated

@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 (2)
apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift (2)

1769-1793: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the duplicate hasAttachments parameter. Swift cannot compile imapMessage with hasAttachments declared at both lines 1763 and 1769.

🤖 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/IMAPMailboxConnectionAdapterTests.swift`
around lines 1769 - 1793, Remove the duplicate hasAttachments declaration from
the imapMessage helper, retaining a single parameter and its existing
propagation into IMAPProviderMessage. Ensure the helper continues to initialize
message.hasAttachments consistently.

1500-1540: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Verify the attachment descriptor for the GreenMail fixture.

This test only verifies hasAttachments. It does not verify that loadMessageBody exposes the expected attachment descriptor. A regression can show an attachment indicator but provide no preview.

Load the fixture body and assert the part ID, filename, MIME type, and byte count. As per path instructions: "Verify externally observable behavior, privacy invariants, error paths, cancellation or restart behavior where relevant, and deterministic isolation."

🤖 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/IMAPMailboxConnectionAdapterTests.swift`
around lines 1500 - 1540, Extend
testSystemClientRecognizesGreenMailAttachmentBodyStructure to load the fixture
body after metadata and verify the exposed attachment descriptor, including part
ID, filename, MIME type, and byte count. Use the existing fixture message and
authorization flow, and retain the hasAttachments assertion while checking the
externally observable attachment details.

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.

Outside diff comments:
In `@apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift`:
- Around line 1769-1793: Remove the duplicate hasAttachments declaration from
the imapMessage helper, retaining a single parameter and its existing
propagation into IMAPProviderMessage. Ensure the helper continues to initialize
message.hasAttachments consistently.
- Around line 1500-1540: Extend
testSystemClientRecognizesGreenMailAttachmentBodyStructure to load the fixture
body after metadata and verify the exposed attachment descriptor, including part
ID, filename, MIME type, and byte count. Use the existing fixture message and
authorization flow, and retain the hasAttachments assertion while checking the
externally observable attachment details.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 333d39cd-497a-4a37-91a9-25e22a4c2c40

📥 Commits

Reviewing files that changed from the base of the PR and between a4d528c and 06c408c.

📒 Files selected for processing (2)
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
📜 Review details
🧰 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/IMAPMailboxConnectionAdapterTests.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/IMAPMailboxConnectionAdapterTests.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/IMAPMailboxConnectionAdapterTests.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
🔇 Additional comments (6)
apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift (5)

1289-1291: Propagate CancellationError before the cache fallback.

This catch block still returns a cached body after cancellation. The existing review comment covers this unchanged issue.


1104-1105: LGTM!


1265-1274: LGTM!


1305-1327: LGTM!


2043-2065: LGTM!

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

660-680: LGTM!

Also applies to: 980-1040, 1286-1423

@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: 41bc0ab2ba

ℹ️ 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/SystemIMAPMailboxClient.swift Outdated
Comment thread apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.swift Outdated

@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/Services/Mailbox/IMAPMailboxConnectionAdapter.swift (1)

2067-2079: 🚀 Performance & Scalability | 🔵 Trivial

Consider the sync-gate hold time for attachment downloads.

The attachment download runs inside syncGate.withLock(connection.id). An attachment can reach 25 MB, so one download can hold the per-connection lock for the whole transfer. During that time syncInbox, continueHistoricalBackfill, prefetchMessageBodies, and other body loads on the same Mailbox Connection wait.

The lock is also what guarantees that cleanup cannot interleave with provider access, so do not remove it without preserving that invariant. One option is to acquire the gate only for authorizationForProviderAccess and to hold a separate download-scoped guard for the transfer, with an explicit re-check of the authorization generation after the download completes.

🤖 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/Mailbox/IMAPMailboxConnectionAdapter.swift`
around lines 2067 - 2079, Reduce the duration of the per-connection sync gate in
the attachment-loading flow around authorizationForProviderAccess and
bodyReader.loadMessageAttachment: do not hold syncGate.withLock through the full
download. Preserve cleanup/provider-access serialization by introducing the
appropriate download-scoped guard, and re-check the authorization generation
after the transfer completes before returning.
🤖 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/SystemIMAPMailboxClient.swift`:
- Around line 189-196: Update the attachment lookup in the guard within the
mailbox attachment download flow to match the selected part by attachment.id
rather than full MailboxMessageAttachment equality. Keep the existing
encodedByteCount extraction and bounds validation unchanged after selecting the
matching part.

---

Outside diff comments:
In
`@apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift`:
- Around line 2067-2079: Reduce the duration of the per-connection sync gate in
the attachment-loading flow around authorizationForProviderAccess and
bodyReader.loadMessageAttachment: do not hold syncGate.withLock through the full
download. Preserve cleanup/provider-access serialization by introducing the
appropriate download-scoped guard, and re-check the authorization generation
after the transfer completes before returning.
🪄 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: a6e65031-8d33-4b46-a80f-d1803a10a874

📥 Commits

Reviewing files that changed from the base of the PR and between 06c408c and c3e7933.

📒 Files selected for processing (5)
  • .changeset/preview-imap-attachments.md
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.swift
  • apps/unwired-mail/unwired-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • docs/adr/0030-gate-incoming-attachment-downloads.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
.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/preview-imap-attachments.md
docs/adr/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Record durable architecture decisions and privacy-boundary decisions in docs/adr/.

Files:

  • docs/adr/0030-gate-incoming-attachment-downloads.md
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Treat docs as product truth, not marketing. Prefer precise domain language
from CONTEXT.md; flag undefined terms, synonym drift, and conflicts with
existing ADRs under docs/adr/. For ADR changes, require a clear decision,
consequences, and privacy or security boundary impact; flag silent
reversals of prior ADRs without an explicit supersession note. For
docs/agents/ and bootstrap docs, verify agent workflow, validation, and
triage guidance stay aligned with AGENTS.md. Flag broken cross-links,
speculative future claims presented as shipped behavior, and docs that
omit the Apple vs TypeScript ownership of a boundary they describe.

Files:

  • docs/adr/0030-gate-incoming-attachment-downloads.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-mailTests/IMAPMailboxConnectionAdapterTests.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.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/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.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/IMAPMailboxConnectionAdapter.swift
  • apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.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
🪛 markdownlint-cli2 (0.23.2)
.changeset/preview-imap-attachments.md

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

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

🔇 Additional comments (7)
apps/unwired-mail/unwired-mail/Services/Mailbox/SystemIMAPMailboxClient.swift (2)

177-217: LGTM!

Also applies to: 266-292, 294-310, 312-349


167-172: 📐 Maintainability & Code Quality

SwiftMail API usage is valid. SwiftMail 1.10.0 exposes all referenced symbols, and fetchPart returns raw transfer-encoded bytes. decoded(for:) performs the single required decoding step.

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

1289-1294: LGTM!

Also applies to: 1307-1330, 1448-1473, 1475-1485, 2005-2014, 2058-2066, 2513-2520

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

1110-1119: 📐 Maintainability & Code Quality | ⚡ Quick win

Assert that the prefetch entry exists before the refresh assertion.

This test targets the refresh branch in IMAPMessageBodyService.loadMessageBody, which refetches when the cached payload reports includesAttachmentMetadata == false. The only assertion about request counts is client.bodyRequestCount == 2.

That count also holds if prefetchMessageBodies stored nothing and the second load was a plain cache miss. The test would then pass without exercising the branch. Capture the cache in the test and assert that an entry exists after the prefetch.

As per path instructions: "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."

♻️ Proposed assertion for the prefetch entry
+    let cache = RecordingIMAPBodyCache()
     let adapter = try makeAdapter(
       authorizationStore: authorizationStore,
+      cache: cache,
       client: client,
       definitions: [definition],
       keyStore: keyStore
     )
     try await adapter.prefetchMessageBodies(
       connection: connection,
       pinnedThreadIds: [message.threadIdentity],
       referenceDate: Date(timeIntervalSince1970: 1_781_200_100),
       session: session
     )
+    `#expect`(
+      try cache.loadMessageBody(
+        productAccountId: session.productAccountId,
+        stableProviderMessageId: message.stableProviderMessageId
+      ) != nil)
     let body = try await adapter.loadMessageBody(message: message, session: session)

Source: Path instructions


979-1012: LGTM!

Also applies to: 1122-1169, 1379-1471, 1980-1980, 2046-2058

.changeset/preview-imap-attachments.md (1)

1-5: LGTM!

docs/adr/0030-gate-incoming-attachment-downloads.md (1)

9-11: LGTM!

@gipity-bot gipity-bot Bot mentioned this pull request Aug 11, 2026
6 tasks done
@gipity-bot

gipity-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Recreated as #438, stacked on #437 so the feature uses SwiftMail end to end and does not restore the removed in-house IMAP transport. The replacement preserves the Attachment Preview scope and links #323. This branch is being kept for history.

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.

Add Attachment Preview support to standards-based mail

1 participant