Offline sat in the middle of Backup & reset, privacy settings were spread
over Reading, Composing and Message list, and the version card took room
from the page list in the sidebar's footer. The search matched letters
inside words ("lock" found Time format and Send Checks) and knew no other
words for a setting ("offline", "autoreply", "logout" found nothing).
- Offline is a page in the top group: Keep mail on this device, what is
kept, Remove everything from this device, and the Outbox as its own
section. Old `/settings/backup/offline` links and the offline notice
land there.
- Privacy holds remote images and trusted senders, read receipts, sender
pictures from the web, the desktop app's usage info and crash reports,
and a link to the push relay on Notifications. Reading, Composing and
Message list keep a link row; old links to those sections land on
Privacy.
- About is the last page and holds the version card; the footer keeps the
Advanced switch.
- The search matches whole words and word starts (substrings only in
Chinese, Japanese and Thai), and each row can carry synonyms in the
user's language. A page matches by its name and keywords or when one of
its rows has every word. Account gets a Sign out row so "logout" leads
somewhere.
- The index test now also fails when a page draws a section the index
doesn't know, and a new test fails on two indexed rows with the same
name. Tests that rendered remote content and sender pictures on Reading
and Message list render Privacy instead, since that's where the rows are.
Fixes from the review of the desktop import.
- A message another program holds is no longer recorded as deleted. Only a
real absence moves the import past it; anything else is tried again and
then reported as a failure, and the next run reads it.
- A resume that can't read some folders names them in a toast with a choice
to stop trying, keeps the job open and retries only those at the next start.
Files chosen by hand in the same dialog are named, since they can't come
back by themselves.
- A wipe or sign-out releases the job in the shell before the record goes.
Leaving the dialog without starting releases the confirmed files. Job
records are per account, and a start is refused while another window holds
that account's job.
- Dedupe goes by the bytes without state headers: a reused Message-ID of the
same length dropped a real message. Older records still count, so nothing
goes in twice.
- A lost connection on a folder or Email/import pauses instead of counting
towards the failures that stop an import.
- The taskbar goes through `lib/desktop/taskbar-progress.ts`.
Test changes: the taskbar test now renders the host, because its helper in
`local-import-job.ts` is gone, and the dedupe tests use `check`, which replaced
`keyFor` and `has`.
The import dialog in the desktop app can now find Thunderbird profiles and
Outlook PST files, show what it found and import what the user confirms.
Browsers behave as before.
- The bridge's `localImport` gives opaque handles and range URLs. The shell
asks natively before it looks, before it reads and before it goes on after
a restart.
- `lib/mail-import` gets a Maildir source and a per-file `folder`, so
Thunderbird's `.sbd` tree maps onto the account's folders. The PST worker
reads through a synchronous range XHR when there is no Blob.
- What was imported is remembered by Message-ID and size per folder, because
compacting a folder changes its checkpoint key and Email/import doesn't
dedupe. It runs from the dialog and on a resume.
- The job's record lives in IndexedDB, since Chromium writes localStorage too
late to survive a kill. Windows wait for the job's lock, so one takes over
when another goes.
- The desktop dialog says how long an import takes at the upload limit, warns
when Thunderbird is running, and turns down .ost files with the reason.
The engine used to be able to lose an import while offline: uploadBlob kept
the file on this device under a stand-in id and reported success. It now
passes `noHold`, Email/import is network only, an offline spell pauses without
using up a message's tries, and a device-only client stops at once. The same
fix applies to imports in the browser.
Review of the local store export. A cancel, sign-out or wipe during the
final step still downloaded the file; now the sink takes the job's signal
and drops the Blob, and a job that outlasts the wait is forgotten when it
ends. Finished exports are in a list a later wipe or sign-out can empty,
also after the dialog closed.
- `localExportAllowed` uses `scopeDecided`, so a domain or group scope
can't be missed before the subject arrives. It's checked before the
save picker, in the same tick as the click.
- WebKit holds the final Blob in memory: exports stop at 1 GB, and a
larger choice or one over `storage.estimate()` is refused up front.
- An empty folder that refuses during a full listing counts as empty,
only when the store's own count agrees. Any other refusal stops the run.
- A picked file goes on an abort or an all-failed run; if it can't, the
dialog names it. Failure texts come from our strings, not the browser's.
- A selection keeps to the chosen folders on a retry.
The test that the organisation shows as the reason now flips the policy
through the policy store, because the old one replaced the guard and so
never reached the watch that a real change goes through. The spec moves
to an account of its own, dso-export: other specs running at the same
time work in ent-dave, and its folder went missing there mid-run.
Firefox and Safari have no save picker, so an export wrote a plain file
into the origin private file system for as long as it ran, and a crash
left a whole mailbox readable there until the next start. The check that
nothing readable sits in OPFS would find it.
The file is now a spool: each piece is sealed with AES-GCM under a key
that exists only in the tab's memory. When the export is done it opens
into a Blob the browser holds, and the spool goes at once.
The test that the private file stays while its download reads it changes
to say the same of the Blob: the file no longer outlives the export, so
what a download depends on is what gets released after the grace period.
Organisation > Devices now sets policy.json `organisation.devices`, each
switch off by default and scoped per domain or group:
- No downloads: attachments and files only preview (reader, conversation
view, Pro tab, list chips, .eml previews, the Files app). Download,
Download all, Open in new tab, Save to Files, drag-out, the browser's
PDF viewer (pdf.js instead) and media download controls go away.
- No export: no .eml export, View source, message drag-out, or folder and
account export; a running export stops and its file is deleted when
the switch turns on. Import stays.
- No printing: no Print buttons in mail, calendar and contacts; Ctrl+P
prints a note; the reader frame prints blank.
- No install prompt: no card, and the browser's own banner is held back.
- Watermark: the user's address and the date over the reader,
previews and every printout (mail, calendar, contacts, plugins).
A message follows its own account's policy; what isn't about one message
follows the strictest signed-in account. Every user path hides its
button, and the anchor and new-tab helpers refuse as a backstop
(lib/download-guard.ts), never the blob readers. The plugin APIs
`ui.downloadFile` and `ui.print` refuse too, and the office editor frame
loses `allow-downloads`.
Two assertions in components/mail/__tests__/blob-routing.test.tsx now
expect `{ kind: 'export' }` as the fifth argument of downloadBlob for an
.eml export: the export says what it is, so the backstop checks "No
export" rather than "No downloads" for it.
- Settings > Backup & reset gets a Mail files section for Import mail
and Export all mail, instead of sitting under settings backup.
- The pause message names Stalwart's 50000000-byte quota as 50 MB, not
48 MB, and the dialog and settings texts mention .ost files.
- The old import's leftovers go: expandImportableEmails and
EML_IMPORT_ACCEPT in lib/eml-import.ts, and the strings
email_viewer.import_email, mailbox_context_menu.import_email and
notifications.import_email_success/_error in all 30 locales.
lib/__tests__/eml-import.test.ts loses the tests of the removed
functions; its #891 checks that an .eml and an .eml in a ZIP keep their
Date header now run against the import's own sources.
- The private file an export writes in Firefox and Safari is deleted
when the app starts and when someone signs in, not only at the next
export, and at once on sign-out. Close keeps it until its download
had time to finish. Each tab holds its own files under a Web Lock, so
one tab's sweep no longer deletes another tab's running export.
- A browser whose private file system can't write files (Safari before
26) is told the export stays in memory instead of being promised a
download.
- The export's live region announces only the phase and pauses; the
count next to it no longer floods screen readers, and the format
group is named once.
- A message whose attachments are bigger than the server takes is
skipped before anything is read (a 1 GB attachment took several GB in
the worker and could crash it), and a worker that stops answering
ends the import with a message instead of a wait that never ends.
- The items after a broken one in a PST folder are counted as
unreadable in the summary instead of disappearing.
- Sent Items and Deleted Items are known from the PST's own entry IDs,
and the Inbox and the others by their names in more languages, so a
PST imported at the top level fills the user's own folders whatever
its language. The hint about that shows only for the top level.
- Message-ID, In-Reply-To and Content-ID from the PST can't carry line
breaks into the rebuilt headers.
A message that failed for a passing reason (a dropped connection, a
server error, an expired token) used to count as done, so continuing
the import skipped it for good, and a slow upload followed by a cancel
imported the messages after it twice.
- The checkpoint keeps every message that is done, also past a gap,
and the next run of the same import tries only the rest again.
- Ten failures in a row, a full mailbox (overQuota) or a refusal
(forbidden) stop the import with a message instead of going through
the whole file.
- An upload without an answer pauses the import instead of failing the
message: 30 seconds, then until the upload window ends.
- Email/import, the export's Blob/get and folder listing pass the
client's own 429 pause by, and a 429 on them doesn't pause the rest
of the app.
- Signing out stops the job of that account only, writes no checkpoint
and imports nothing more afterwards; another account's sign-out
leaves the job alone.
- One tab imports a file at a time (Web Locks), and Import waits until
the dialog knows whether an earlier run can be continued.
The import dialog tests now wait for the Import button to be enabled
before clicking it, since it stays off during that lookup.