Commit Graph
44 Commits
Author SHA1 Message Date
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 656d4b46d7 Optimize deployment, fix E2E flakiness, and implement database-backed threading
- Optimize task deploy-android with marker files and source/generate tracking.
- Fix flaky Android E2E test with pumpAndSettle and safety delays.
- Implement global CrashScreen and error handlers in main.dart.
- Refactor threading to use a persistent Threads table for performance.
- Add database indexes and migration for schema v18.
- Enhance coverage gate with ghost path checks and increased coverage (82%).
2026-05-07 22:07:54 +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 Opus 4.7 fb767a8489 fix: don't resurrect locally-deleted IMAP message on next sync
The incremental IMAP sync issued `UID ${lastUid + 1}:*` to look for new
mail. RFC 3501 §6.4.4 reverses `n:*` to `*:n` when n exceeds the largest
UID, so a server with one message at UID 1 and `lastUid=1` returned UID 1
for `UID 2:*` — re-fetching and re-inserting a row the user had just
deleted locally (whose pending change had not yet flushed).

`_fetchAndUpsertImap` now looks up the UIDs in the mailbox that have a
pending `delete` or `move` queued and skips the insert for those. The
existing `UID n:*` query is left intact so freshly-delivered SMTP mail
keeps driving StreamBuilder rebuilds in the E2E flow.

Regression test in `email_repository_imap_test.dart` deletes a synced
message and calls `syncEmails` directly — exactly what the in-app sync
button does — and asserts the row stays gone with the pending change
still queued.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 14:02:12 +02:00
Thomas GüttlerandClaude Sonnet 4.6 2074046bb3 feat: split search query into words for AND semantics
Searching for "foo bar" now matches emails containing both words rather
than the exact phrase. Each whitespace-separated term generates its own
IMAP criterion (OR SUBJECT "w" TEXT "w") — multiple top-level IMAP criteria
are ANDed by the protocol — and its own Drift LIKE clause for the local DB.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:27:14 +02:00
Thomas GüttlerandClaude Sonnet 4.6 8a51496181 fix: INBOX sync misses SMTP-delivered mail due to Stalwart CONDSTORE bug
Stalwart 0.14.x does not increment HIGHESTMODSEQ when new mail arrives
via SMTP delivery, so the incremental sync's CONDSTORE fast-path saw
serverModSeq == storedModSeq and returned early — silently skipping
UID SEARCH and missing any newly received messages.

Fix: remove the early-return fast-path. Incremental sync now always
runs UID SEARCH UID ${lastUid+1}:* to discover new messages. CONDSTORE
is still used for the flag-refresh gate (only runs when modseq changed),
which is its correct, narrower role.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 17:55:52 +02:00
Thomas GüttlerandClaude Sonnet 4.6 39c3d1ea1a feat: show email preview snippet in thread list tiles
Added preview field to EmailThread (from latest email's preview via
_groupIntoThreads). Thread tiles now show subject + one-line body snippet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:43:35 +02:00
Thomas GüttlerandClaude Sonnet 4.6 e3ba18285d refactor: enforce always_use_package_imports across all lib files
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>
2026-04-24 16:30:59 +02:00
Thomas GüttlerandClaude Sonnet 4.6 281acdf665 fix: log errors in silent catch (_) blocks instead of swallowing them
JMAP push stream failures and UI-layer search/discovery errors now emit
a log line via the project logger so they are visible during debugging.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:28:42 +02:00
Thomas GüttlerandClaude Sonnet 4.6 e7d172eba5 feat: safety hardening before real account use
- Fix BODY[] → BODY.PEEK[] for body and attachment fetches so opening an
  email does not silently set \Seen as an IMAP side-effect
- deleteEmail now moves to Trash (if a trash-role mailbox exists) instead
  of hard-deleting with UID EXPUNGE; falls back to EXPUNGE only when
  already in Trash or no Trash folder is found
- Add confirmation dialogs to all three delete entry-points: detail-view
  delete button, batch-delete in list view, and swipe-to-delete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 15:48:22 +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üttler aa8d59453b avoid accidental complete delete. 2026-04-24 07:15:15 +02:00
Thomas Güttler b814a3736b fix test. 2026-04-23 17:43:20 +02:00
Thomas GüttlerandClaude Sonnet 4.6 16607f7ea0 test: add JMAP Stalwart integration tests; fix Email/set destroy array
- New test/integration/email_repository_jmap_test.dart covers full sync,
  incremental sync, server-side deletion reconciliation, getEmailBody
  with cache, sendEmail, and flushPendingChanges (flag, delete, move).
- Fix: Email/set destroy must be an array per RFC 8620 §5.3; was passing
  a bare string which Stalwart silently rejected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 21:45:42 +02:00
Thomas GüttlerandClaude Sonnet 4.6 e7c87f6d60 feat: JMAP attachment download via blob URL
Adds downloadBlob() to JmapClient (RFC 8620 §6 downloadUrl template)
and routes downloadAttachment() to it for JMAP accounts. IMAP path
is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 21:22:32 +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 2bd082e90e fix: wrap IMAP email batch insert in a single transaction
Without a transaction, N individual inserts each re-acquire the SQLite
write lock, creating a window where a concurrent sync-log write hits
SQLITE_LOCKED. The whole batch then throws, no checkpoint is saved, and
the inbox ends up with only the emails that inserted before the failure.
Wrapping in one transaction makes the batch atomic and holds the lock
for a single commit instead of N.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 12:11:44 +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 6a457a9f7a fix: IMAP full sync via UID SEARCH+FETCH; add sync log UI
- Replace full-sync fetchMessages(1:*) with UID SEARCH ALL + UID FETCH
  so every message gets a reliable UID on all servers
- Guard CONDSTORE select on server capability to avoid BAD from
  servers that do not advertise CONDSTORE/QRESYNC
- Add SyncLogEntry model + observeSyncLogs stream to SyncLogRepository
- Add SyncLogScreen with per-entry duration/error display
- Wire history icon in SettingsScreen → /accounts/:id/sync-log route
- Fix FakeImapClient to expose initialized serverInfo via field override

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 07:43:30 +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 93ac5afbcf feat: conflict resolution hardening — server-wins policy, max-retry eviction
- Check notUpdated/notDestroyed per-item errors in Email/set; throw
  JmapSetItemException for permanent failures (notFound, forbidden) so
  they are discarded immediately rather than retried
- Add _maxChangeAttempts=5 constant; _recordChangeError() evicts the
  pending-change row when attempts reach the limit, preventing unbounded
  queue growth from transient errors
- Both IMAP and JMAP flush paths now use _recordChangeError() consistently
- Document server-wins conflict-resolution policy in DB-SYNC.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 21:05:48 +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 8d8dbc33db feat: JMAP send via EmailSubmission/set; role column on Mailboxes
- sendEmail dispatches on account type: IMAP keeps SMTP+APPEND path,
  JMAP chains Email/set create + EmailSubmission/set in one API call
- Sent mailbox looked up by role='sent' from local DB so sent mail lands
  in the right folder
- JmapClient gains uploadUrl/eventSourceUrl/capabilities from session,
  supportsSubmission getter, withSubmission flag on call(), and uploadBlob()
  for attachment upload before send
- Mailboxes table gains nullable role column (schema v8); _upsertJmapMailboxes
  persists role from JMAP Mailbox/get response

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 17:41:21 +02:00
Thomas GüttlerandClaude Sonnet 4.6 7e34ca45de feat: JMAP body caching during sync and ifInState conflict detection
- Include bodyValues/textBody/htmlBody/attachments in every Email/get call
  during syncEmails; _upsertJmapEmails writes to email_bodies so first open
  is instant even for freshly synced messages
- Extract _parseJmapBody helper shared by sync path and on-demand fetch
- Add JmapStateMismatchException; _applyPendingChangeJmap passes ifInState
  and returns newState; on stateMismatch the local checkpoint is cleared so
  the next cycle does a full re-sync before retrying the mutation
- Update DB-SYNC.md to reflect what has been implemented

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 17:21:08 +02:00
Thomas GüttlerandClaude Sonnet 4.6 8a0e09301b feat: JMAP Email/query pagination via calculateTotal
_jmapFullEmailSync now loops with position offset until all emails are
fetched.  Each iteration sends calculateTotal=true; if the accumulated
position < total, another page is requested.  The Email state from the
first page is saved so incremental sync picks up exactly from there.

Servers that omit total (non-RFC 8620) are handled gracefully: the loop
stops after the first page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 17:01:14 +02:00
Thomas GüttlerandClaude Sonnet 4.6 cc12a53bc1 feat: JMAP getEmailBody via Email/get with bodyValues
getEmailBody now dispatches on account type. For JMAP accounts it calls
Email/get with fetchHTMLBodyValues and fetchTextBodyValues, extracts the
first text and HTML body part via partId references, and caches the
result in email_bodies — same as the IMAP path.

Before this change, JMAP body requests fell through to the IMAP path
which would fail for accounts without IMAP credentials.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:55:25 +02:00
Thomas GüttlerandClaude Sonnet 4.6 bf66b2118e feat: IMAP incremental sync via sync_state checkpoints
_syncEmailsImap now stores {uidValidity, lastUid} per mailbox in the
sync_state table after each full sync.  Subsequent syncs only fetch
UIDs newer than lastUid (UID N+1:*) and then do an ALL search to
reconcile remote deletions — avoiding a full re-download on every poll.

When UID validity changes the stale local emails are discarded and a
full re-sync is performed automatically.

fake_imap: add uidValidityResult + searchCallQueue so tests can feed
distinct responses to consecutive uidSearchMessages calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:52:30 +02:00
Thomas GüttlerandClaude Sonnet 4.6 091c848d0e feat: IMAP durable outbound queue via pending_changes
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>
2026-04-19 16:48:13 +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 2efeba9d2e feat: JMAP Email sync — full and incremental (Step 5)
EmailRepositoryImpl.syncEmails now dispatches on account type.
For JMAP accounts:
- First run: Email/query (filtered by mailbox, limit 500) + Email/get
  via back-reference → upsert emails, persist state.
- Subsequent runs: Email/changes → fetch new/updated via Email/get,
  delete destroyed rows, update state in sync_state.

Maps JMAP keywords ($seen, $flagged), mailboxIds, addresses, and
hasAttachment to the existing Emails table. uid stored as 0 for
JMAP emails (unused; JMAP operations go through Email/set in Step 7).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:19:03 +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 b144dba5ec feat: username field separate from email, try-connection button, JMAP auth verification
- 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>
2026-04-18 15:37:11 +02:00
Thomas Güttler c7a121e386 stalwart-dev/integration_ui_test.sh working! great! 2026-04-18 12:05:20 +02:00
Thomas Güttler 9ce598d21c task check, working again. 2026-04-17 10:05:31 +02:00
Thomas Güttler 99a46e1589 test. 2026-04-16 15:14:18 +02:00
Thomas GüttlerandClaude Sonnet 4.6 7c000dcee5 Add IMAP search: server-side OR SUBJECT/TEXT, inline results in email list
- EmailRepository: add searchEmails(accountId, mailboxPath, query)
- EmailRepositoryImpl: UID SEARCH with OR SUBJECT/TEXT criteria,
  fetch ENVELOPE+FLAGS for matching UIDs
- EmailListScreen: toggle search bar in AppBar; submit triggers server
  search; results replace the stream list; ESC/back closes search
- Refactored list into _buildList() shared by stream and search views
- README/PLAN.md updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 11:13:33 +02:00
Thomas GüttlerandClaude Sonnet 4.6 4e03483126 Fix API mismatches, lint violations, and test failures
- enough_mail: use uidFetchMessage/uidMarkSeen/uidMarkFlagged/uidMove/
  uidMarkDeleted/uidExpunge, remove non-existent isUidSequence param,
  fix SmtpClient construction and use quit() not disconnect()
- Drift: add @DataClassName('MailboxRow') to avoid ugly 'Mailboxe',
  alias core model imports to resolve type name conflicts
- EmailsCompanion.insert: uid/receivedAt are required, not Value<T>
- Lint: remove unrecognised rules (prefer_const_collections,
  avoid_returning_null_for_future), add missing mounted guards after await
- Tests: fix html_utils expectations to match trim() behaviour,
  add explicit Map casts in email_model_test for avoid_dynamic_calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 08:21:14 +02:00
Thomas GüttlerandClaude Sonnet 4.6 72e2b599bf Fix API mismatches, add Linux desktop entry point, reply prefill
API fixes (against vendored enough_mail 2.1.7):
- listMailboxes() returns List<Mailbox> directly — remove .mailboxes
- Use statusMailbox() for unread/total counts per mailbox
- fetchMessages(MessageSequence.fromAll(), ...) replaces nonexistent
  fetchAllMessages(); fetchMessage() takes isUidSequence flag
- FetchImapResult.messages are already MimeMessages — no need to
  re-parse rawData; use msg.decodeTextPlainPart() / decodeTextHtmlPart()
- msg.hasAttachments() (method) not msg.body?.hasAttachments (field)
- SmtpClient clientDomain = sender domain, not display name; quit()
  instead of nonexistent disconnect(); STARTTLS wrapped in try/catch
- ContentInfo.size is nullable; use a.fileName / a.size getters

Other fixes:
- main.dart: move sync start to initState, not build()
- account_list_screen: remove dead/invalid Riverpod select() code
- account_sync_manager: subscribe to account changes; cancel sub on
  dispose; use Future.any([newMsg, 25-min timeout]) for IDLE
- email_repository: add getEmail(id) to interface + impl
- email_detail_screen: load header + body together via Future.wait;
  reply prefills To/Cc/Subject correctly
- compose_screen + router: thread prefillCc through

Add Linux desktop entry point:
- linux/CMakeLists.txt, main.cc, my_application.h/.cc (GTK3 runner)

Add flake.lock (generated by nix flake update).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 07:51:52 +02:00
Thomas GüttlerandClaude Sonnet 4.6 5ebda521d6 Initial Flutter/Dart port of SharedInbox
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>
2026-04-16 07:35:56 +02:00