## 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
- 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).
- 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.
- 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.
- 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%).
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
_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>
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>
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>
- 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>
- 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>
Long-press any email to enter selection mode. Tap items to toggle
checkboxes. AppBar shows count and close button. Bottom bar provides
Archive, Delete, Mark as spam, and Move to folder actions for all
selected emails. Swipe gestures are disabled while selecting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Add live-connection Sieve script management for JMAP accounts via
RFC 9661: list, create, edit, delete, and activate scripts.
Accessible via "Email filters" in the account popup menu.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use xdg-open directly on Linux for opening attachments (fixes 'file type not supported' error)
- Add mime package for comprehensive MIME type detection in compose screen
- Show file size and MIME type for attachments in compose screen
- Add open/preview button for attachments in compose screen
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
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>
- FolderDrawer widget: shows account name/email in header, sorted
folder list with unread badges; tapping a folder navigates via
context.go (replaces route rather than stacking)
- MailboxListScreen and EmailListScreen both get the drawer (burger
icon auto-appears) and the account display name in the AppBar
- Folder sort logic moved to folder_drawer.dart (shared)
- accountByIdProvider added to di.dart for reactive account lookup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tapping an account tile now opens the folder list rather than jumping
straight to INBOX. The redundant "All mailboxes" popup entry is removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>