Commit Graph
114 Commits
Author SHA1 Message Date
Thomas SharedInbox e52a386c33 fix: correct column naming in raw SQL indices (snake_case) 2026-05-10 22:26:33 +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 0895a79a33 fix: make snooze migration idempotent to avoid duplicate column error 2026-05-10 22:06:42 +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 0855049c30 ui: move undo log access to drawer 2026-05-10 18:43:19 +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 c1fbf7ece1 Merge branch 'feat/issue-7-undo-log' 2026-05-10 14:45:11 +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 3ca71cd4e5 feat: add hyperlinks to Codeberg commits in ChangeLog (Issue #9) 2026-05-10 13:18:35 +02:00
Thomas SharedInbox 2ecb29d6ee feat: implement in-app ChangeLog from git history 2026-05-10 11:23:39 +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
guettlibotandThomas SharedInbox 6d58ee1e00 Fix Issues 1, 2, and 3: Headers, Undo, and Crash Reporting (#6)
## Overview
This PR implements several fixes and enhancements requested in the latest session:

### Fixes
1. **Issue 1: Raw Email Headers**
   - Added database support for raw headers.
   - Added a new Headers tab in the email detail screen with a zebra-colored table display.
2. **Issue 2: Exception on Undo of Delete**
   - Added `toJson` and `fromJson` to `EmailAddress` model to fix serialization during undo.
3. **Issue 3: Crash Reporting**
   - Added a button to the Crash Screen to report issues directly on Codeberg.

### Infrastructure
- Added Nix experimental features check to `Taskfile.yml` to ensure a consistent dev environment.

## Verification
- Manually verified the Headers display on Linux.
- Verified Undo for IMAP and JMAP accounts.
- Verified the Crash Screen button.

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/6
2026-05-09 18:49:34 +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 674b21298d feat: implement exponential backoff and permanent error handling for sync engine 2026-05-09 13:25:59 +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 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 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 Sonnet 4.6 d4d61b2b39 feat: sync-now button on sync log screen
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>
2026-05-05 07:23:34 +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 44d02afc46 feat: render HTML email bodies via flutter_html
Email detail screen now renders the message htmlBody with the flutter_html
widget when present, falling back to SelectableText for plain-text-only mail.
Remote http(s) images are blocked by default to defeat tracking pixels; an
opt-in "Load remote images" button reveals them per-screen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:10:18 +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 Opus 4.7 2e2b7c3d9f fix: Android E2E aliceTile race + bundle deploy-android infra
The Android UI integration test failed at tap(aliceTile) with "0 widgets"
even though pumpUntil had just found the tile. On the slow software-rendered
emulator the route-pop animation finalises during pumpUntil's trailing 300 ms
settle, briefly leaving the tile out of the tree. Re-confirm with a second
pumpUntil before the tap.

Bundles the previously uncommitted infra changes that make task deploy-android
run end-to-end inside nix develop: Linux desktop runtime libs + GL software
rendering env in flake.nix, path_provider_android pin to <2.3 to avoid the
libdartjni SIGSEGV, deferred DB-path resolution after WidgetsFlutterBinding,
+iglx for xvfb-run, platform-tools on PATH, and a single pre-commit script
replacing the dart-format / task-check-fast pair.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 12:36:30 +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 e22418c6dd feat: add Email filters entry to FolderDrawer for JMAP accounts
The Sieve script editor was only reachable via the hidden popup menu on
the account list. Adding an 'Email filters' tile to the drawer makes it
discoverable from any mailbox view for JMAP accounts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:40:01 +02:00
Thomas GüttlerandClaude Sonnet 4.6 527683172c feat: bulk actions on search results via long-press selection
Long-pressing a search result enters selection mode; tapping further
results toggles them. The existing bottom bar (Archive, Delete, Mark
as spam, Move to folder) operates on the selected emails via a new
_selectedSearchIds set. _selectedEmailIds returns the right IDs for
each mode (search vs. normal thread list).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:32:44 +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 0103454e31 feat: add 'All accounts' entry to FolderDrawer for back-navigation
Users had no obvious way to return to the account list after opening an
account. Adding an 'All accounts' tile at the top of the drawer (visible in
both MailboxListScreen and EmailListScreen) lets users navigate to /accounts
from anywhere inside an account.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:20:16 +02:00
Thomas GüttlerandClaude Sonnet 4.6 c4928ef362 fix: Android E2E — robust account-tile finder, search debounce, DEFUNCT error filter
- pumpUntil uses ListTile-scoped finder so it doesn't exit early when
  'Alice' is still in the form's EditableText before navigation pops
- tap(aliceTile) reuses that same finder instead of a second find.text
- EmailListScreen search bar adds onChanged debounce (300ms) so the
  test never needs receiveAction(TextInputAction.search), which caused
  a keyboard-dismiss animation that triggered layout overflow in
  disposed render objects
- FlutterError.onError filter in the test suppresses DEFUNCT/DISPOSED
  overflow errors from Android's route-teardown layout passes
- integration_android_test.sh: force-stop + pm clear before uninstall
  so stale app data can't bleed into subsequent runs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 20:04:31 +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 d3646e350b fix: surface sync errors via snackbar instead of silently swallowing them
The sync button used a fire-and-forget lambda — any exception from
syncEmails was discarded with no feedback. Now it awaits the call and
shows a snackbar on failure, making errors visible in the UI and in tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 21:38:40 +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 71692665cc feat: quoted reply body and Forward button in email detail
Reply and reply-all now prefill the compose body with the original message
quoted as plain text (> lines). New Forward button sets Fwd: subject and
the same quoted body, leaving To/Cc empty for the user to fill.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:48:19 +02:00
Thomas GüttlerandClaude Sonnet 4.6 cdd5e5a6fc feat: mark as unread button in email detail screen
Added mark_email_unread_outlined icon to the toolbar. Calls setFlag(seen:false)
then pops back to the list so the email reappears as unread.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:46:09 +02:00
Thomas GüttlerandClaude Sonnet 4.6 14b1ee14ac feat: pull-to-refresh on email list screen
Wrapped the stream body in RefreshIndicator calling syncEmails.
Empty-state is now a scrollable ListView so the pull gesture triggers
even when the folder contains no messages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:44:53 +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 eff2940bb8 refactor: extract TryConnectionButton widget shared by account screens
Moved the result banner + spinner/button UI into a new stateless
TryConnectionButton widget, used by both add and edit account screens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:35:10 +02:00
Thomas GüttlerandClaude Sonnet 4.6 d1f77d3eb6 refactor: extract _batchMoveToRole helper in email_list_screen
_batchArchive and _batchMarkSpam shared identical role-lookup+move logic;
collapsed into a single _batchMoveToRole(role, notFoundMessage) helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:32:22 +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