Commit Graph
2339 Commits
Author SHA1 Message Date
Linus Rath 889f6ffbd1 fix: fail requests under way when the desktop app closes its connections, and forget client certificates per account 2026-11-04 00:56:09 +01:00
Linus Rath 8549e3c4c6 feat: translate the desktop app's client certificate picker and forget chosen certificates at sign-out and account switch 2026-11-03 23:21:56 +01:00
Linus Rath 54bda3ef3d Merge branch 'feat/do-packaging' into feat/desktop-offline
# Conflicts:
#	components/desktop/desktop-host.tsx
#	lib/desktop/languages.ts
2026-11-03 22:02:10 +01:00
Linus Rath 43ba229c7d Merge branch 'feat/do-perf-guard-2' into feat/desktop-offline 2026-11-03 21:50:37 +01:00
Linus Rath 2b4a1171ab fix: show the spelling and unsent-mail labels in the desktop app instead of their raw keys 2026-11-03 21:50:09 +01:00
Linus Rath 7509bd9f53 Merge branch 'main' into feat/desktop-offline 2026-11-03 21:37:32 +01:00
Linus Rath d651ae857c Merge remote-tracking branch 'origin/main' into feat/do-perf-guard 2026-11-03 21:34:59 +01:00
Linus Rath a89cae5f28 fix: show the page skeleton from the first frame again, with or without an app lock 2026-11-03 21:33:51 +01:00
Linus Rath b4bcce2b01 Merge branch 'main' into feat/desktop-offline 2026-11-03 19:53:36 +01:00
Linus Rath 423fe28b0b Merge branch 'main' into feat/do-ci-fix 2026-11-03 19:50:46 +01:00
Linus Rath 1efe9bc8b4 test: tell reminders of two accounts apart by uid, since one meeting in both rings once 2026-11-03 19:49:57 +01:00
Linus Rath d890addbf4 test: register devices a second apart in the cap test, since rows of one millisecond have no oldest 2026-11-03 19:49:56 +01:00
Linus Rath ac2f7cba2b test: allow keystore as the one bare plugin permission, since it has no actions and the S/MIME manifest uses the name 2026-11-03 19:49:56 +01:00
Linus Rath fe3c213cb9 test: spell out the device policy defaults, since the offline caps are numbers where 0 means no cap 2026-11-03 19:49:55 +01:00
Linus Rath 984e6fa8d9 fix: label device and pop-out requests in metrics and error reports 2026-11-03 19:49:55 +01:00
Linus Rath 3f59af9c31 fix: delete offline mail right away when the local store doesn't answer 2026-11-03 19:49:55 +01:00
Linus Rath 26c009c302 feat: send the updater's translated text with the desktop menu labels 2026-11-03 19:45:23 +01:00
Linus Rath 529d334496 Merge branch 'fix/ui-bug-hunt' 2026-11-03 19:44:46 +01:00
Linus Rath b8560579a4 Merge branch 'feat/do-import' into feat/desktop-offline
# Conflicts:
#	lib/desktop/types.ts
2026-11-03 19:44:43 +01:00
Linus Rath a96c66fa5e Merge branch 'main' into feat/do-packaging
# Conflicts:
#	components/settings/general-settings.tsx
#	lib/desktop/types.ts
2026-11-03 19:37:41 +01:00
Linus Rath 1e9afb733a fix: let the Docker image build see the script its Next config imports 2026-11-03 19:19:35 +01:00
Linus Rath b92b488e39 fix: stop a local import losing messages on a held file and name what a resume leaves out
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`.
2026-11-03 19:17:38 +01:00
Linus Rath 5849f9dc42 Merge branch 'main' into feat/desktop-offline 2026-11-03 19:07:04 +01:00
Linus Rath 151cedf992 Merge branch 'feat/do-app-lock' into feat/desktop-offline 2026-11-03 18:46:05 +01:00
Linus Rath ef3e428ff3 Merge branch 'feat/desktop-offline' into feat/do-import
# Conflicts:
#	components/mail-transfer/mail-transfer-host.tsx
#	lib/desktop/types.ts
2026-11-03 18:34:24 +01:00
Linus Rath deb73cc6e2 Merge branch 'feat/do-native-ui' into feat/desktop-offline 2026-11-03 18:28:49 +01:00
Linus Rath 084f93a786 feat: import Thunderbird and Outlook data from this computer in the desktop app
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.
2026-11-03 17:57:54 +01:00
Linus Rath 335e1edc10 Merge branch 'feat/desktop-offline' into feat/do-app-lock
# Conflicts:
#	components/settings/general-settings.tsx
#	hooks/use-calendar-alerts.ts
#	lib/desktop/app-lock-state.ts
#	lib/desktop/policy-schema.ts
#	lib/desktop/types.ts
2026-11-03 17:52:30 +01:00
Linus Rath 99bc974f8b Merge branch 'feat/do-telemetry' into feat/desktop-offline
# Conflicts:
#	lib/desktop/types.ts
2026-11-03 17:42:32 +01:00
Linus Rath dcfe25c754 Merge branch 'feat/do-store-export' into feat/desktop-offline 2026-11-03 17:39:54 +01:00
Linus Rath 3d5b02b75f fix: stop a page reporting once consent goes, and let nothing on disk aim the desktop app's telemetry
A review found that an open page kept sending to a DSN after consent was
taken back, that a state file or config file could name where telemetry
goes, and that `BULWARK_TELEMETRY=off` did nothing on the desktop build.
A due heartbeat now waits a random 30 s to 10 min, a refusal from the
collector isn't retried, crash files go at sign-out and account switch,
and server builds parse stacks as they did.

Existing assertions changed in the desktop telemetry tests: the catch-up
wait is now a range, the endpoint comes from the shell, and the platform
is `desktop` against the collector's patched branch.
2026-11-03 17:38:04 +01:00
Linus Rath 530364b11c fix: stop a cancelled or wiped export from downloading, and say why an export stopped
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.
2026-11-03 17:29:42 +01:00
Linus Rath e3dec865da fix: let the webmail build again with the device registry's rate limit out of its route 2026-11-03 17:19:39 +01:00
Linus Rath 3f264367af fix: harden the app lock: forget in an order that can't leave a sign-in without a PIN, hear wipes and clear subjects while locked, and refuse obvious PINs 2026-11-03 17:08:02 +01:00
Linus Rath 289cce9bd5 Merge branch 'feat/do-reminders' into feat/desktop-offline
# Conflicts:
#	lib/offline/offline-mode.ts
2026-11-03 16:58:11 +01:00
Linus Rath 383e579eec feat: run the app to the top of the window, with the window's controls in its own toolbars
The desktop app and an installed PWA had a row across the whole window
with the title and the window controls. That row is gone. The rail,
sidebar, list and reader run to the top of the window in their own
colours, and the controls sit in the top row of whatever is rightmost.

- `lib/window-controls-overlay.ts` finds and marks those rows before
  paint, and they reserve the controls' width through
  `--titlebar-right-inset`
- the rows and the rail are the drag area, everything clickable is
  `no-drag`, and an invisible band lets a page with no row still move
- settings and the admin shell get a row only under the overlay, Pro's
  tab strip and Mountain View's bar match the controls' height
- Alt floats the menu over the top left, in `window-menu-bar.tsx`
- skeletons carry the same marks, so nothing shifts when the app loads
2026-11-03 16:52:42 +01:00
Linus Rath aad0b61a63 Merge branch 'feat/desktop-offline' into feat/do-telemetry 2026-11-03 16:51:58 +01:00
Linus Rath af6cdfb997 fix: keep unsent mail through the idle and session limits, and mark a signed-out account in the switcher
The timers used to delete everything an account kept, unsent mail included.
They now follow the refused-login rule: sign out and hide, the next sign-in
brings it back, otherwise it goes after 30 days. A login without "Remember
me" that ends with the browser, an admin wipe and a wipe from settings don't
change.

- `wipeOnRefusedLogin` is now `wipeOnForcedSignOut`, since it also covers the
  limits. The server's `SESSION_TOO_OLD` goes the same way, as `session`.
- A refused account beside one that stays signed in shows in the switcher as
  needing sign-in, with the count and the date, and gets one toast.

Existing tests changed because the rule did: the session timer tests expected
a plain `logoutAll`, and the auth store tests expected no hook at the session
limit.
2026-11-03 16:36:13 +01:00
Linus Rath f21198b804 Merge branch 'feat/desktop-offline' into feat/do-app-lock 2026-11-03 16:24:34 +01:00
Linus Rath 71bd7af93d Merge branch 'feat/do-remote-wipe' into feat/desktop-offline 2026-11-03 16:20:09 +01:00
Linus Rath 3b49b1109e chore: name who acts on the keystore origins policy, as the shell's copy of the schema does 2026-11-03 16:15:02 +01:00
Linus Rath fbd51599aa Merge branch 'feat/desktop-offline' into feat/do-store-export 2026-11-03 16:11:27 +01:00
Linus Rath 79014a63b2 Merge branch 'feat/desktop-offline' into feat/do-packaging
# Conflicts:
#	lib/desktop/types.ts
#	locales/ar/common.json
#	locales/ca/common.json
#	locales/cs/common.json
#	locales/da/common.json
#	locales/de/common.json
#	locales/en/common.json
#	locales/es/common.json
#	locales/et/common.json
#	locales/fa/common.json
#	locales/fr/common.json
#	locales/he/common.json
#	locales/hu/common.json
#	locales/it/common.json
#	locales/ja/common.json
#	locales/ko/common.json
#	locales/lv/common.json
#	locales/mn/common.json
#	locales/nb/common.json
#	locales/nl/common.json
#	locales/pl/common.json
#	locales/pt/common.json
#	locales/ro/common.json
#	locales/ru/common.json
#	locales/sk/common.json
#	locales/th/common.json
#	locales/tr/common.json
#	locales/uk/common.json
#	locales/vi/common.json
#	locales/zh-TW/common.json
#	locales/zh/common.json
2026-11-03 16:09:04 +01:00
Linus Rath 3c3f6016b9 test: catch a wipe heard twice, a row put back and a mark left by a recovered refusal 2026-11-03 16:04:14 +01:00
Linus Rath a52cd8c829 fix: save no empty file when an export could write no message at all 2026-11-03 15:57:19 +01:00
Linus Rath 9376978f0f Merge branch 'feat/do-policies' into feat/desktop-offline 2026-11-03 15:55:31 +01:00
Linus Rath 03cdf69bf2 feat: let desktop users opt in to usage info and crash reports, each with its own switch 2026-11-03 15:55:17 +01:00
Linus Rath 8b3cbd930e feat: translate the desktop updater's menu items and dialogs 2026-11-03 15:42:41 +01:00
Linus Rath e94219dafa fix: keep the example address out of the code the outgoing host guard reads 2026-11-03 15:42:19 +01:00
Linus Rath 3cda05a1fa feat: export mail kept on this device to mbox or EML zip, also offline 2026-11-03 15:40:57 +01:00