Commit Graph
30 Commits
Author SHA1 Message Date
Bot of Thomas Güttler 7096c27ede feat(U6): show sync status indicator in email list app bar (#29) 2026-05-14 04:23:07 +02:00
Bot of Thomas Güttler 2715c1613f feat(U4): background sync and local notifications for new mail (#28) 2026-05-14 04:06:35 +02:00
Bot of Thomas Güttler 0e291b509b feat(U2): sync local drafts with IMAP Drafts folder (#27) 2026-05-14 00:27:47 +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 efdcab74d7 setup nix in CI, and reformat. 2026-05-12 21:55:06 +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 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 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 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 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 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 fca9e1aecf fix: fire-and-forget sync log write in error catch blocks
Previously the catch block awaited _syncLog.log(), so a second DB error
(e.g. lock contention) would escape the catch, skip the log() call and
the backoff delay, and surface as an unhandled exception. Using .ignore()
means a log write failure is silently dropped but the error is always
printed to stdout and the backoff always runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 13:28:39 +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 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 db548a7d8b feat: cross-protocol sync log — record success/failure for every sync cycle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 17:09:13 +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 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 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ü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 20f180b76a misc. 2026-04-16 13:21:36 +02:00
Thomas GüttlerandClaude Sonnet 4.6 03d35387f7 Linting, tests, README, CI, and code quality improvements
Linting:
- analysis_options.yaml: flutter_lints base + 20 additional rules
  (prefer_single_quotes, avoid_print, cancel_subscriptions,
  unawaited_futures, empty_catches, always_declare_return_types, …)
- unused_import / dead_code treated as errors

pubspec.yaml:
- Remove 6 unused packages: freezed, freezed_annotation, json_annotation,
  json_serializable, riverpod_generator, collection
- Add test: ^1.25.0 for pure-Dart unit tests

Testable utilities (extracted from screen code):
- lib/core/utils/html_utils.dart — htmlToPlain()
- lib/core/utils/format_utils.dart — fmtSize()
- lib/core/utils/logger.dart — thin debugPrint wrapper

Unit tests (test/unit/, no device required):
- html_utils_test.dart — 13 cases covering tags, entities, edge cases
- format_utils_test.dart — B / KB / MB / GB formatting
- email_model_test.dart — EmailAddress JSON roundtrip, toString, EmailDraft
- account_sync_backoff_test.dart — exponential backoff + clamping

Taskfile:
- task test → dart test test/unit/ (fast, no device)
- task test-flutter → flutter test (widget tests)
- task check → analyze + unit tests in parallel

README rewrite:
- Covers user flow (download, add account)
- Developer flow: nix develop, direnv allow, task codegen, task check
- Platform status table (Linux done, others pending)
- Project layout diagram
- Schema-change workflow

CI (.github/workflows/ci.yml):
- analyze-and-test job: flutter analyze + dart test on every push/PR
- integration job: Nix + Stalwart on push to main
- build-linux job: flutter build linux --release

Logging:
- Replace all bare catch (_) {} with log() calls
- Sync backoff errors now print account email + retry delay
- STATUS failures on \Noselect mailboxes logged at debug level
- STARTTLS failures logged before continuing without TLS

.gitignore:
- Add .direnv/, .flutter-plugins-dependencies
- Add all platform generated-plugin wiring files
- Add .env / .env.local
- Add linux/build/

.env.example: documents optional STALWART_PORT overrides

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