Commit Graph
42 Commits
Author SHA1 Message Date
Bot of Thomas Güttler 9081b452f3 feat: add structured search with visual filter builder (#469) 2026-06-07 04:38:28 +02:00
Bot of Thomas Güttler 3bd404f0cf feat: add 'Create new folder' option to Move To Folder dialog (#423) 2026-06-05 18:53:36 +02:00
Bot of Thomas Güttler c1d314a621 feat: combined inbox as the default startup view (#376) (#379) 2026-06-04 02:46:59 +02:00
Thomas Güttler 8ea8d71f42 fix: format, analyze-fix and update mocks 2026-06-02 17:10:16 +02:00
Thomas Güttler 1e2d1b6063 chore: migrate to SOPS and SSH for Dagger engine access 2026-06-02 11:10:29 +02:00
Bot of Thomas Güttler 4e32984ecc fix: prompt to create or pick folder when archive is missing (#286) (#290) 2026-05-27 19:06:37 +02:00
Bot of Thomas Güttler f57a8c502d feat: syncLog add Copy button, stack trace, isPermanent (#266) (#269) 2026-05-26 07:55:07 +02:00
Bot of Thomas Güttler 0293cb5845 fix: stop retrying on MissingPluginException from flutter_secure_storage (#200) (#209) 2026-05-24 08:50:06 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 517f799b99 feat: apply local Sieve rules after sync (#119)
- Add LocalSieveApplied table (schema v32) keyed by (accountId, messageId)
  so each email is processed by Sieve at most once, even across restarts.
- Implement EmailRepository.applySieveRules(): loads the active local Sieve
  script, runs the interpreter against new INBOX emails, and queues pending
  move/delete/flag_seen changes for any matched rules.
- Wire applySieveRules() into both _AccountSync._sync() and
  _JmapAccountSync._sync() after the per-mailbox email sync loop.
- Make _flushPendingChangesImap() treat NONEXISTENT / not-found errors as
  silent no-ops (counts as flushed) so a second device racing on the same
  email does not accumulate retries.
- Add migration test assertions and a dedicated unit test suite covering
  rule matching, deduplication, discard, and multi-email processing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 10:34:21 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 69e358204d fix(undo): keep undo log entry and fix IMAP UID mismatch after sync (#81)
Two fixes for the UndoLog:

1. Don't delete the original undo log entry when undo is performed.
   The entry stays in the log alongside the new inverse action, so
   the user can retry the undo if it was silently reverted by an
   IMAP sync.

2. Fix IMAP UID mismatch: after an IMAP move is applied on the server
   the email gets a new UID in the destination folder. The undo service
   now looks up the email by its RFC 2822 Message-ID when the original
   row is gone, so the reverse-move pending change carries the correct
   UID and actually succeeds on the server.

Add findEmailByMessageId to EmailRepository interface and impl.
Add a regression test that simulates the UID change scenario.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 10:46:12 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 0ccf7b51fa fix: fetch original RFC822 from server in Show Raw Email (#84)
Instead of reconstructing the message from the local DB, fetch the
original bytes live from IMAP (BODY.PEEK[]) or JMAP (Email/get blobId
→ downloadBlob) so the view shows the true unmodified message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 09:27:12 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 02b0fec0b6 feat(compose): autocomplete To/Cc from local address history
Adds RawAutocomplete<EmailAddress> to the To and Cc fields in the
compose screen. As the user types (minimum 2 chars), suggestions are
fetched from the local DB by searching from/to/cc columns of cached
emails. Selecting a suggestion appends it to any existing addresses
already in the field (comma-separated).

New repository method searchAddresses() returns deduplicated
EmailAddress objects matching the query string.

Closes #11

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 21:30:17 +02:00
Bot of Thomas Güttler 499774d1a6 feat: add 'Mark all as read' to mailbox overflow menu (U8) (#48) 2026-05-14 10:58:33 +02:00
Bot of Thomas Güttler 4f16587564 feat(P2): paginate email list — default 50 threads, Load more button (#42) 2026-05-14 10:09:05 +02:00
Bot of Thomas Güttler fc592c475f feat(R4): dismissible sync error banner in email list (#23) 2026-05-13 23:14:44 +02:00
Bot of Thomas Güttler beae8d8843 feat(R2): force full sync escape hatch in account edit screen (#22) 2026-05-13 22:57:36 +02:00
Thomas SharedInbox b7ff02711b feat: implement snooze feature for IMAP and JMAP
- 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.
2026-05-10 21:50:13 +02:00
Thomas SharedInbox d405b37308 fix: implement global undo UI and optimistic IMAP moves for better UX 2026-05-09 15:35:17 +02:00
Thomas SharedInbox e2759ac062 fix: restore Undo functionality for IMAP accounts by preserving data 2026-05-09 14:03:52 +02:00
Thomas SharedInbox 03a68a00c6 feat: implement periodic sync reliability verification and health indicator 2026-05-09 09:47:42 +02:00
Thomas Güttler 43e1744614 feat: implement optimized Undo for delete and move actions
- Added UndoService with 10-action history stack.
- Integrated Undo Snackbar into EmailListScreen and EmailDetailScreen.
- Added EmailRepository.cancelPendingChange to optimize undo by removing
  unsynced local mutations.
- Fixed sorting bug in compareMailboxes for unknown roles.
- Increased unit coverage to 83% with new model and utility tests.
- Verified with full test suite (task check).
2026-05-08 11:14:54 +02:00
Thomas Güttler 8d268f1165 Implement multi-account search and improve repository fakes
- Extended search to support global queries across all accounts.
- Updated SearchScreen to handle optional account context and unified results.
- Centralized mailbox comparison logic in Mailbox model.
- Added copyWith to Account model.
- Fixed race conditions and incorrect overrides in unit, widget, and integration tests.
- Reached 80% unit test coverage.
2026-05-08 01:01:18 +02:00
Thomas Güttler cd0892763c Implement Thread View UI and repository support
- Created ThreadDetailScreen with expandable email cards and HTML support.
- Added observeEmailsInThread to EmailRepository and implementation.
- Updated navigation in EmailListScreen to route multi-message threads to the new view.
- Updated test mocks (FakeEmailRepository) across unit, widget, and integration tests.
- Documented progress in done.md and updated next.md.
2026-05-08 00:14:50 +02:00
Thomas Güttler 2f58fb7a08 fix: IMAP attachments (size/download) and immediate sync for deletions
- 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.
2026-05-06 09:58:42 +02:00
Thomas GüttlerandClaude Sonnet 4.6 0980ef2d08 feat: thread view with reconcile guard against empty IMAP server response
- Add EmailThread model and observeThreads() grouping emails by RFC 2822
  References/In-Reply-To headers (IMAP) or native threadId (JMAP)
- Store threadId/messageId/inReplyTo/references in DB (schema v14)
- Switch EmailListScreen to thread-grouped view; flag icon preserved
- Guard _reconcileDeletedImap against wiping local cache when server
  returns 0 UIDs (network glitch / buggy IMAP server)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 15:12:04 +02:00
Thomas GüttlerandClaude Sonnet 4.6 544cd9b335 feat: swipe to archive/delete in email list, with role-based mailbox lookup
- Add `role` field to Mailbox model (surfacing what was already stored in DB)
- IMAP sync: map enough_mail special-use flags (RFC 6154) to role strings
- JMAP: role was already synced to DB, now passed through _toModel()
- Add MailboxRepository.findMailboxByRole() for role-based lookup
- EmailListScreen: swipe right → archive, swipe left → delete (Dismissible)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 08:31:22 +02:00
Thomas GüttlerandClaude Sonnet 4.6 c9645f79dc feat: search everywhere — folders, addresses, and messages
Adds per-account incremental search (3+ chars, 300 ms debounce) that
queries local DB and shows results grouped: Folders → Addresses → Messages.
Address results link to a dedicated filtered-by-address email list screen.
Routes: /accounts/:id/search and /accounts/:id/emails/by-address/:addr.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 21:11:27 +02:00
Thomas GüttlerandClaude Sonnet 4.6 643bb47f87 feat: verbose protocol logging per account (schema v13)
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>
2026-04-21 16:34:55 +02:00
Thomas GüttlerandClaude Sonnet 4.6 a27342c7e9 feat: add per-mailbox breakdown to sync log (schema v12)
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>
2026-04-21 16:19:40 +02:00
Thomas GüttlerandClaude Sonnet 4.6 2e869194e9 test: verify sync errors always appear in the sync log
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>
2026-04-21 13:33:20 +02:00
Thomas GüttlerandClaude Sonnet 4.6 1ab915d73a feat: extend sync log with skipped count and bytes transferred
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>
2026-04-21 11:32:50 +02:00
Thomas GüttlerandClaude Sonnet 4.6 0435129434 feat: verbose sync log — protocol, emails fetched, mailboxes, pending changes
- 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>
2026-04-21 08:52:58 +02:00
Thomas GüttlerandClaude Sonnet 4.6 be56232f00 feat: linting + format automation + IMAP integration tests against Stalwart
- Add `format` task (fvm dart format .) and pre-commit dart-format hook
- Fix pre-commit task-check hook to use nix develop --command task
- Add CI format-check step (dart format --set-exit-if-changed .)
- Enable directives_ordering, curly_braces_in_flow_control_structures,
  discarded_futures, unnecessary_await_in_return, require_trailing_commas
- Apply 330 trailing-comma fixes (dart fix --apply) across all files
- Wrap intentional fire-and-forget futures with unawaited() to satisfy
  discarded_futures lint in account_sync_manager, email_repository_impl,
  and UI screens
- Add test/integration/email_repository_imap_test.dart: 8 tests against
  real Stalwart (sync, body fetch+cache, send, search, flag/move/delete)
- Remove 14 fake-IMAP unit tests migrated to Stalwart integration tests
- Fix flushPendingChanges move test: create Trash folder before IMAP MOVE
- Lower coverage gate 85%→80%: IMAP paths now tested by Stalwart (real),
  not counted in unit-test lcov
- Delete LINTING.md (plan fully executed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 18:08:09 +02:00
Thomas GüttlerandClaude Sonnet 4.6 d5a5c7fbe3 feat: IMAP CONDSTORE fast-path, JMAP blob TTL, offline compose queue UI
- IMAP CONDSTORE (RFC 7162): skip sync when HIGHESTMODSEQ is unchanged;
  refresh only changed flags via CHANGEDSINCE on incremental sync
- JMAP blob expiry: re-fetch email bodies older than 7 days (schema v8→v9
  adds nullable cachedAt column to email_bodies)
- Offline compose queue: expose stuck pending_changes rows via
  observeFailedMutations / retryMutation / discardMutation; surface them
  in a FailedMutationBanner on the mailbox list screen
- Unit tests for all three features (236 passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 06:32:33 +02:00
Thomas GüttlerandClaude Sonnet 4.6 795001d268 feat: JMAP push via EventSource instead of polling
- 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>
2026-04-19 17:48:40 +02:00
Thomas GüttlerandClaude Sonnet 4.6 4bf157c550 feat: IMAP sync all folders, not just INBOX
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>
2026-04-19 16:41:54 +02:00
Thomas GüttlerandClaude Sonnet 4.6 0797dd914b feat: JMAP outbound changes via pending_changes queue (Step 7)
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>
2026-04-19 16:25:44 +02:00
Thomas GüttlerandClaude Sonnet 4.6 559eb9a467 feat: JMAP background sync worker (Step 6)
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>
2026-04-19 16:22:09 +02:00
Thomas GüttlerandClaude Sonnet 4.6 2f1924be9c feat: email attachments — send, download and open
- Add file_picker and open_file dependencies
- EmailDraft gains attachmentFilePaths; EmailAttachment gains fetchPartId
- sendEmail attaches files via MessageBuilder.addFile()
- downloadAttachment fetches the specific MIME part from IMAP, caches to
  local filesystem; subsequent calls return the cached file without a
  network round-trip
- ComposeScreen: attach-file button + removable attachment list
- EmailDetailScreen: per-attachment download/open button with spinner
- 3 new unit tests covering send-with-attachment, download, and cache hit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 17:04:25 +02:00
Thomas GüttlerandClaude Sonnet 4.6 442c3c4087 feat: add-account wizard, edit account, inbox tap, connection status
- 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>
2026-04-18 15:13:47 +02:00
Thomas Güttler c6be26623d better coverage. 2026-04-16 13:44:55 +02:00
Thomas Güttler 746f3c373c tests. 2026-04-16 11:48:37 +02:00