Compose attachments, account-backup export/import and database-snapshot
export still opened desktop file dialogs directly, so on mobile they
reported "no picker" even though the host provides a DocumentProvider.
- DocumentProvider::import_many() imports several documents for one action
(default: repeat import() until the picker is cancelled; the desktop
provider uses one multi-select dialog). Imported documents stay alive in
the composer until Core has staged the attachments.
- Compose attachments use import_many() instead of rfd::FileDialog.
- Backup export/import stage one app-private file and hand it to the
provider; the existing import logic keeps its synchronous shape on the UI
thread executor.
- Database snapshots keep Core's verified snapshot, archive it as one zip
(hosts export files, not folders) and pass it to the provider. Staging
stays outside the data/cache roots Core requires.
- Desktop behavior is unchanged: same dialogs and file names.
Email links called webbrowser::open directly, which fails on platforms that
cannot spawn helper processes (there is no xdg-open and friends everywhere).
The provider authorization flow already had a platform-aware opener; this
generalizes the path for arbitrary external addresses:
- browser::set_external_url_opener() lets a platform integration install its
own opener (first registration wins).
- browser::open_external() uses that opener when present and falls back to
the webbrowser crate otherwise (no behavior change on desktop).
- open_email_link uses it instead of calling webbrowser directly.
When the open message left the list (archive, trash, spam, move, or a
sync removal), the background refresh cleared the selection and the
reader fell back to the first row, losing the user's place.
Remember the previous visible order and select the next surviving row
below the removed message, or the nearest one above when it was last.
Mirrors the jmap_adoption_candidate tests: the server's rewritten copy is
adopted by subject plus send time, the +/-2 minute window is inclusive, two
candidates are rejected as ambiguous, and neither incoming rows nor outgoing
rows that already carry a uid are ever candidates.
A body that never decodes (a message the server expunged while its row
lingers, an undecodable part) was retried every 60 s forever: the drain
loop could neither persist it nor clear it, so "made no progress"
warnings repeated each minute. Attempts now widen the retry window
(1 min x5, then 15 min x15, then 24 h), so a poison message parks instead
of looping.
Adds repo::sync_failures::content_attempts plus a unit test.
Outlook rewrites Message-IDs in some folders, which made the header-search
idempotence guard miss an already-appended copy (retries appended twice)
and left sent rows without a uid, so the next Sent-folder sync inserted the
server copy as a second message.
The append action now records the copy's uid: an existing match from the
header search is reused; otherwise UIDNEXT is captured before the append and
the pre-append value is adopted when exactly one message appeared (a bigger
delta falls back to a re-search). Both branches log at INFO with the uid.
Microsoft's SMTP path auto-saves a copy of every submission into Sent, and
the app appended its own copy as well, so a send produced two Sent messages.
Outlook also rewrites Message-IDs in some folders, breaking the header-search
idempotence guard: retries appended twice and the Sent-folder sync imported
the server copy as a third message (the queued row carried no uid).
- Microsoft joins Gmail in skipping the local append; the server's copy is
the single source of truth for those accounts.
- The Sent-folder sync adopts the server copy into the queued outgoing row
when the Message-ID was rewritten (same folder, is_outgoing, uid IS NULL,
same subject and a +/-2 minute send-time window; two candidates are
rejected as ambiguous), mirroring jmap_adoption_candidate.
Existing duplicates on the server are user data and are left in place.