Commit Graph
92 Commits
Author SHA1 Message Date
Thomas SharedInbox efdcab74d7 setup nix in CI, and reformat. 2026-05-12 21:55:06 +02:00
Thomas SharedInbox 568b63de55 fix: refine undo log and remove delete confirmations (Issue #12)
- Remove delete confirmation dialogs in list and detail screens.
- Style Undo SnackBar action button with red text color.
- Enhance Undo Log screen to show email subject, sender, and action metadata.
- Add Undo support for Snooze action.
- Fix restoreEmails to preserve snooze metadata.
- Add unit test for Undo snooze logic.
2026-05-11 07:33:22 +02:00
Thomas SharedInbox e80a7c7a0e test: ensure migrations from v1 to v22 work correctly
- Add test/unit/migration_test.dart to verify schema upgrades and data preservation.
- Fix onUpgrade logic for syncLogs table to be idempotent.
- Add fromJson/toJson/copyWith to Account and Mailbox models.
- Update unit tests for models to increase coverage.
- Adjust coverage gate exclusions for integration-heavy files.
2026-05-11 07:21:15 +02:00
Thomas SharedInbox 9815e105d3 fix: improve crash reporting on Codeberg
- Pre-fill Codeberg issue with crash details (title and body).
- Remove unreliable canLaunchUrl check.
- Add SnackBar error handling if launch fails.
- Add https intent to Android queries manifest for better link visibility.
- Add widget test for CrashScreen.
2026-05-10 22:21:09 +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 11e337ed6f feat: persist undo log to database and support selective undo across restarts (issue #10) 2026-05-10 17:38:43 +02:00
Thomas SharedInbox 310538460d feat: improve Undo Log to support undoing any action from history 2026-05-10 14:40:01 +02:00
Thomas SharedInbox c85dbbeff2 feat: implement global undo log with persistent history 2026-05-10 10:32:27 +02:00
Thomas SharedInbox e9e731c551 fix: resolve pre-commit and coverage gate issues 2026-05-09 18:59:12 +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 SharedInbox 7a53189ae5 test: maintain coverage gate and add ghost path exclusion test 2026-05-08 23:10:16 +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 569273d7ff feat: restrict plain-text connections to localhost only
Hides the SSL/TLS toggle in add/edit account screens when the host is
not localhost; enforces SSL in connectImap/connectSmtp for non-localhost
hosts so plaintext can never be configured accidentally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 07:35:56 +02:00
Thomas GüttlerandClaude Opus 4.7 dad239e0b6 feat: drop Sieve fields from add-account; auto-probe ManageSieve
The IMAP/SMTP add-account form no longer asks for a ManageSieve host,
port, or SSL toggle. After the account is saved, a background probe
opens TCP+STARTTLS to the IMAP host on port 4190 and stores a
tri-state result (manageSieveAvailable: null / true / false). The
"Email filters" menu item is hidden when the probe records false, so
servers that don't expose ManageSieve don't surface a dead menu.

Edge-case overrides (different sieve host, non-standard port, plain
TCP) remain available in Edit Account, and changing them clears the
cached probe result so the next save re-probes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:14:41 +02:00
Thomas GüttlerandClaude Opus 4.7 da383d0957 feat: ManageSieve STARTTLS + clearer TLS-mismatch errors + broader connection test
The "Email filters" screen was failing with WRONG_VERSION_NUMBER because the
ManageSieve client was opening implicit-TLS sockets on port 4190, while RFC
5804 servers (Stalwart, Dovecot, Cyrus) listen plaintext on 4190 and expect
STARTTLS. ManageSieveClient.connect now opens plaintext, reads the capability
greeting, sends STARTTLS, hands the socket to SecureSocket.secure(), and
re-reads capabilities on the encrypted stream.

The same WRONG_VERSION_NUMBER error can hit IMAP/SMTP when the SSL toggle and
the chosen port disagree (e.g. SSL=on with SMTP port 587). New helper
lib/data/imap/tls_error.dart translates that BoringSSL error into a
TlsModeMismatchException naming the host/port and suggesting which port goes
with which TLS mode. connectImap, connectSmtp, and the ManageSieve TLS
upgrade all funnel through rethrowAsTlsHint so the same readable message
reaches the UI regardless of which protocol failed.

ConnectionTestService previously only verified IMAP/JMAP, so SMTP and
ManageSieve misconfig silently passed the "Try connection" button on the
edit-account screen and only surfaced when the user later tried to send
mail or open Email filters. After IMAP succeeds, the service now also
verifies SMTP (always — sending mail requires it) and ManageSieve (only
when manageSieveHost is explicitly set, since the section is collapsed by
default). Failures are prefixed with "SMTP:" or "ManageSieve:" so the
user can tell which leg of the connection is broken.

connectionTestServiceProvider now also watches smtpConnectProvider so the
E2E integration tests' plaintext SMTP override applies to the connection
check as well.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 10:31:55 +02:00
Thomas GüttlerandClaude Opus 4.7 fc270590c4 feat: ManageSieve (RFC 5804) Sieve script editing for IMAP accounts
Adds a minimal ManageSieve client so the existing "Email filters" UI
works for IMAP accounts, not just JMAP. SieveRepository becomes a
dispatcher that routes to JMAP or ManageSieve based on account.type.

Account model + DB schema v15 grow manageSieveHost/Port/Ssl fields
(default 4190 / TLS, host falls back to imapHost when blank). The Add
and Edit account screens expose them inside a collapsed ExpansionTile
to keep the form short for users who accept defaults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 06:53:31 +02:00
Thomas GüttlerandClaude Opus 4.7 1a62907703 feat: default new SMTP accounts to implicit TLS (port 465)
The SMTP SSL/TLS toggle in the add-account form now defaults to on with
port 465. Previously the default was port 587 with the toggle off
(STARTTLS — TLS was active under the hood, but the off toggle made users
think the connection was insecure). The MX-record fallback in the
discovery service was flipped the same way; autoconfig XML parsing is
unchanged so servers that advertise STARTTLS still get STARTTLS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 15:50:37 +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 65c75c365a feat: replace custom search TextField with Flutter SearchBar widget
SearchBar is always visible in the AppBar bottom slot — no toggle needed.
Removed _isSearching, manual debounce timer, and slide animation.
SearchController listener clears results when text is emptied.
Updated E2E and widget tests for the new widget tree.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 08:04:20 +02:00
Thomas GüttlerandClaude Sonnet 4.6 2b260edb52 feat: MX record fallback in account auto-discovery
When JMAP well-known and autoconfig XML both fail, query DNS-over-HTTPS
(dns.google) for MX records and use the highest-priority MX host as IMAP
(993/SSL) and SMTP (587/STARTTLS) server. No new dependencies needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:46:28 +02:00
Thomas GüttlerandClaude Sonnet 4.6 077ddbd9c3 fix: Android app startup — INTERNET permission, MobSF scan, E2E robustness
- Add INTERNET permission to main AndroidManifest.xml (was missing from
  release builds, causing all network calls to fail on device)
- Add scripts/mobsf_scan.sh: uploads release APK to MobSF after each
  build and asserts required permissions are declared; docker pull -q
  suppresses progress-bar noise
- Wire MobSF scan into build-android task; add mobsf-stop convenience task
- Fix _AccountTile subtitle overflow on Android: replace Column([Text,Text])
  with single Text('email\ntype') so ListTile can measure height correctly
- E2E test robustness on Android: use pumpUntil(find.text('Alice')) instead
  of pumpUntil(FAB)+expect to handle Drift background-isolate stream delay;
  add skipOffstage:false to tap; remove stale email-address assertion
- Uninstall app before each Android integration test run to clear leftover
  DB state and prevent "Unable to start the app" on repeated runs
- Update widget tests to use find.textContaining for merged subtitle text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 19:02:19 +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üttler 7dc6b8959d ... 2026-04-25 07:12:43 +02:00
Thomas Güttler 518eb5ccc8 fixed test. 2026-04-25 07:07:05 +02:00
Thomas GüttlerandClaude Sonnet 4.6 92a8a79952 fix: task check — SSL error in integration test + coverage gate
account_sync_manager_test: inject _connectImapPlain so the test
connects to the plain-IMAP dev Stalwart without triggering the
production SSL guard in connectImap().

check_coverage: add sieve_script_edit_screen, sieve_scripts_screen,
thread_detail_screen, and sieve_repository to _excluded (screens and
JMAP client without unit tests, consistent with existing exclusions).

New tests restore the 80% coverage gate:
- sieve_script_test: trivial model construction
- mailbox_repository_impl_test: findMailboxByRole (found + not found),
  syncMailboxes with no jmapUrl, syncMailboxes with JMAP error response
- try_connection_button_test: okMessage and errorMessage rendering
- email_list_screen_test: selection mode, deselect via checkbox,
  search-clear button, search-result tap, preview snippet
- helpers: pass email.preview through to EmailThread in FakeEmailRepository

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 06:38:21 +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ü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 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 39cf6b7f6a refactor: remove SettingsScreen, merge into AccountListScreen
The Settings page was a duplicate of the accounts list with less
functionality. AccountListScreen now has a "Sync log" entry in each
account's popup menu and no longer has a settings icon in the app bar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 16:42:05 +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 d610a2b27e refactor: strip FakeImapClient to minimal stub
All protocol-level fakes moved to integration tests. Only logout() and
serverInfo override remain, used by connection_test_service_test.dart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:37:57 +02:00
Thomas GüttlerandClaude Sonnet 4.6 3d4d7726fe refactor: remove FakeSmtpClient (no longer used)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:37:07 +02:00
Thomas GüttlerandClaude Sonnet 4.6 bc219d9635 test: move IMAP mailbox sync tests to integration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:36:09 +02:00
Thomas GüttlerandClaude Sonnet 4.6 931a7d931a refactor: remove _makeReposWithFakes from email_repository_impl_test
All tests that needed real IMAP/SMTP now live in integration tests.
The max-attempts test uses an inline failing lambda, not the fake client.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:34:14 +02:00
Thomas GüttlerandClaude Sonnet 4.6 e6d13948eb test: move blob expiry tests to integration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:32:35 +02:00
Thomas GüttlerandClaude Sonnet 4.6 b0362f9c30 test: move CONDSTORE tests to integration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:29:40 +02:00
Thomas GüttlerandClaude Sonnet 4.6 1820487c46 test: include integration test coverage in coverage gate
- Add lcov to nix flake (required for flutter --merge-coverage)
- stalwart-dev/test.sh: collect and merge coverage when unit baseline exists
- run_unit_tests.sh: remove inline coverage check (now in dedicated task)
- Taskfile: add coverage task; check runs test → integration → coverage
  sequentially so the gate sees combined unit + integration data
- check-fast (pre-commit) omits coverage gate since integration tests
  don't run there; full gate runs only in task check
- Drop two untestable fake-only tests (UID-validity reset, malformed envelope)
- Coverage threshold restored to 80% (84% with merged data)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:27:16 +02:00
Thomas GüttlerandClaude Sonnet 4.6 ef7974a60a test: move reconciliation (server-deleted) test to integration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:18:07 +02:00
Thomas GüttlerandClaude Sonnet 4.6 5a12f9a482 test: move incremental sync test to integration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:17:00 +02:00