- Add snoozedUntil and snoozedFromMailboxPath to Emails table.
- Implement snoozeEmail and wakeUpEmails in EmailRepository.
- Update IMAP and JMAP flush logic to handle snooze/unsnooze.
- Update sync logic to parse snz: keywords from server.
- Add SnoozePicker widget and integrate into UI.
- Add unit tests for Snooze logic.
- Fix IMAP attachment sizes showing as '0 B' by falling back to decoded content length.
- Fix IMAP attachment downloads failing for single-part fetches by falling back to root message.
- Implement immediate server-side sync for deletions/flags using a new onChangesQueued stream.
- Automate FVM SDK setup and add 'task setup'.
- Make MobSF optional in build-android to handle environment restrictions.
- Update test fakes to match EmailRepository interface changes.
Adds a sync icon to the AppBar of the sync log screen. Tapping it wakes
the account's idle/wait loop immediately and shows a spinner until the
next log entry arrives.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added lint rule to analysis_options.yaml and ran dart fix --apply to convert
125 relative imports in 33 files to package:sharedinbox/... style.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When account.verbose is true, raw IMAP/JMAP protocol traffic is captured
via a Zone, redacted of credentials (LOGIN password, AUTHENTICATE tokens),
and stored in the sync log entry for display in the sync log screen.
- DB schema v13: adds verbose column to accounts, protocolLog to sync_logs
- IMAP: Zone print-capture feeds ImapClient isLogEnabled output
- JMAP: JmapClient.call() writes request/response bodies to zone buffer
- Sync log screen: shows a monospace "Protocol log" block when present
- Edit account screen: adds verbose toggle with warning subtitle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each sync cycle now records per-mailbox fetched/skipped/bytes in a new
sync_log_mailboxes table and displays a collapsible "Per mailbox" section
in the sync log screen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add _CapturingSyncLogRepository and two tests (IMAP + JMAP) that assert
a failed sync cycle produces an error entry in the sync log. Also
replace .ignore() in the catch blocks with a proper try-catch so the
sync log write is genuinely attempted and any secondary failure is
logged to stdout rather than silently dropped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the catch block awaited _syncLog.log(), so a second DB error
(e.g. lock contention) would escape the catch, skip the log() call and
the backoff delay, and surface as an unhandled exception. Using .ignore()
means a log write failure is silently dropped but the error is always
printed to stdout and the backoff always runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Track how many emails were already up-to-date (skipped) and the
approximate bytes transferred per sync cycle. SyncEmailsResult
accumulates fetched/skipped/bytes across mailboxes; DB schema v11
adds emailsSkipped and bytesTransferred columns to sync_logs.
SyncLogScreen shows "X new · Y up-to-date · took Zs" in the tile
subtitle with full detail rows in the expansion panel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- syncEmails/syncMailboxes/flushPendingChanges now return int counts
- SyncLogs DB schema v10: adds protocol, mailboxesSynced, pendingFlushed
- SyncLogEntry carries all new fields; AccountSyncManager collects and logs them
- SyncLogScreen uses ExpansionTile with per-row detail rows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add watchJmapPush(accountId, password) to EmailRepository; IMAP and
JMAP-without-push return Stream.empty() so callers fall through to polling
- EmailRepositoryImpl opens an SSE (text/event-stream) connection to the
server's eventSourceUrl; yields void on each StateChange event; properly
cancellable via StreamController.onCancel
- _JmapAccountSync._wait() subscribes to watchJmapPush and races it against
the 30 s poll timer and the stop signal — whichever fires first unblocks
the next sync cycle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
setFlag/moveEmail/deleteEmail for IMAP accounts now enqueue to
pending_changes (with uid + mailboxPath in the payload) and apply an
optimistic local update, instead of calling the IMAP server directly.
flushPendingChanges dispatches on account type: JMAP uses the existing
Email/set path; IMAP opens one connection and drains all queued changes.
Connection failure marks every queued row with an incremented attempt
count so retries work correctly.
_AccountSync._sync() now calls flushPendingChanges before syncing so
queued mutations are delivered on the next poll.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move ImapConnectFn typedef to imap_client_factory so it can be shared.
Inject it into AccountSyncManager/_AccountSync so tests can substitute a
no-op instead of hitting a real IMAP server.
_AccountSync._sync() now iterates all mailboxes from the repository after
syncMailboxes, mirroring the JMAP loop that was already in place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For JMAP accounts, setFlag/moveEmail/deleteEmail now write to the
pending_changes table instead of making direct server calls, enabling
offline-first mutation with durable retries.
flushPendingChanges() drains the queue at the start of each JMAP
sync cycle via Email/set (flag updates use keyword patches; move
updates mailboxIds; delete uses Email/set destroy). On failure the
attempt count and last error are recorded; the change remains queued.
Local DB is updated optimistically on mutation so the UI responds
immediately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AccountSyncManager now starts a _JmapAccountSync loop for JMAP accounts
alongside the existing _AccountSync for IMAP accounts.
_JmapAccountSync:
- Syncs mailboxes then emails for each known mailbox per cycle.
- Polls every 30 seconds (no IDLE for JMAP; EventSource deferred).
- Reuses the same exponential backoff (5–300 s) on failure.
- stop() interrupts the poll wait immediately via a Completer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Account model gains `username` field (default empty → falls back to email then local-part)
- ConnectionTestService returns the effective username that succeeded; tries email then local-part when blank
- JMAP connection probe uses Basic-auth GET to /.well-known/jmap (401/403 = auth failure)
- IMAP/SMTP factory passes explicit username parameter
- Add/edit account screens show username field and "Try connection" button
- EditAccountScreen reuses stored password when no new password is entered
- Unit tests for ConnectionTestServiceImpl (IMAP + JMAP paths, fallback logic)
- Fix unit test lambda signatures for updated ImapConnectFn/SmtpConnectFn
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add account wizard: email-first flow with JMAP/IMAP auto-detection
via well-known URLs; falls back to manual type selection
- Fix JMAP connection probe: GET session URL with Basic auth instead
of the API endpoint, so 401 reliably signals bad credentials
- Account list tile: tap → open INBOX directly; popup menu for
all mailboxes / edit / delete (with confirmation dialog)
- Show account type (JMAP/IMAP) and async connection status per tile:
spinner while checking, green check on success, red error on failure
- Add EditAccountScreen: edit name, password, server settings; runs
connection test only when password is changed
- Fix GTK window initialisation order so app starts with correct size
- Fix 42 lint issues (avoid_redundant_argument_values,
unnecessary_non_null_assertion, unawaited_futures)
- 147 tests, 87% coverage, task check green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
IMAP/SMTP email client with offline-first architecture:
sync engine writes to Drift (SQLite), UI reads reactively
from the local DB. enough_mail vendored under packages/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>