Commit Graph
33 Commits
Author SHA1 Message Date
Thomas SharedInboxandClaude Sonnet 4.6 58272186c8 feat(U1): show Unsubscribe chip for emails with List-Unsubscribe header
- Add listUnsubscribeHeader nullable text column to Emails table (schema v23)
- Parse List-Unsubscribe header from IMAP (BODY.PEEK[HEADER.FIELDS]) and JMAP (header:List-Unsubscribe:asText property)
- Show ActionChip in EmailDetailScreen that launches the unsubscribe URI (prefers mailto:, falls back to https:)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 00:05:05 +02:00
Thomas SharedInbox efdcab74d7 setup nix in CI, and reformat. 2026-05-12 21:55:06 +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 e52a386c33 fix: correct column naming in raw SQL indices (snake_case) 2026-05-10 22:26:33 +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 11e337ed6f feat: persist undo log to database and support selective undo across restarts (issue #10) 2026-05-10 17:38:43 +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 03a68a00c6 feat: implement periodic sync reliability verification and health indicator 2026-05-09 09:47:42 +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ü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 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 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 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 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 44d5307ff8 fix: enable WAL mode and busy_timeout on the SQLite database
Multiple account sync loops share one DB. Without WAL, a write
transaction from one account blocks reads from another, causing
SQLITE_BUSY (code 5) errors even on SELECTs.  WAL mode lets readers
and writers proceed concurrently.  busy_timeout = 5 s makes SQLite
retry instead of immediately surfacing SQLITE_LOCKED on the rare
writer-writer conflict that WAL doesn't eliminate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 13:55:47 +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 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 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 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 c6fb5154fb feat: add pending_changes table (Step 2 — outbound sync queue)
Protocol-agnostic queue for local mutations (flag, move, delete) that
need to be sent to the server. Enables offline-first behaviour: changes
are written here first and drained by the sync worker. Tracks attempt
count and last error for durable retries.

DB schema bumped to v6.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:08:17 +02:00
Thomas GüttlerandClaude Sonnet 4.6 475ba34d28 feat: add sync_state table (Step 1 — DB foundation for incremental sync)
Protocol-agnostic checkpoint table stores one state token per
(account_id, resource_type). JMAP uses the opaque state string from
Mailbox/get and Email/get; IMAP will use a JSON checkpoint per mailbox.

DB schema bumped to v5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:05:31 +02:00
Thomas GüttlerandClaude Sonnet 4.6 e1e95e97ee feat: draft auto-save in compose screen
- Add Drafts table (schema v4 migration) with autoincrement id,
  accountId, replyToEmailId, to/cc/subject/body text, updatedAt
- DraftRepository interface + DraftRepositoryImpl (Drift)
- draftRepositoryProvider wired in di.dart
- ComposeScreen debounces saves (2 s after last keystroke), shows
  transient "Saved" indicator, restores the latest matching draft on
  open when no prefill fields are provided, deletes draft on send
- 6 new unit tests for DraftRepositoryImpl
- New widget test verifying draft restore behaviour
- FakeDraftRepository added to widget test helpers
- draft_repository.dart added to coverage no-code exclusion list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:06:02 +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üttlerandClaude Sonnet 4.6 33d1e21bc9 perf: cut integration-ui test time from 250s to 28s
- Fix HOME override that caused FVM to re-download 220MB Flutter SDK on
  every run; use XDG_DATA_HOME instead to isolate app data without
  touching HOME
- Switch DB path from getApplicationDocumentsDirectory() to
  getApplicationSupportDirectory() so XDG_DATA_HOME isolation works and
  stale accounts don't leak between test runs
- Replace fixed pump(5s/3s) waits with pumpUntil() polling at 200ms so
  tests stop waiting as soon as the UI is ready (23s of dead wait → 8s)
- Add timing instrumentation (ts() in shell, _log()/Stopwatch in Dart)
- Fix CI integration-ui job: was mixing subosito flutter with fvm flutter;
  now uses fvm consistently with actions/cache for ~/.fvm, ~/.pub-cache,
  and build/linux

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