Commit Graph
4653 Commits
Author SHA1 Message Date
Linus Rath 5467f5db93 fix: let the Lite host scan know the desktop app's dictionary host 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 135d1f000d fix: keep the plugin side panel open while the next message loads 2026-11-03 19:49:54 +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 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 deb73cc6e2 Merge branch 'feat/do-native-ui' into feat/desktop-offline 2026-11-03 18:28:49 +01:00
Linus Rath 88f4b20487 fix: open the calendar from a reminder without a full page load 2026-11-03 18:02:03 +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 dc9e0aec26 test: wait out the page's own reloads in the app lock's offline and 5xx cases 2026-11-03 17:41:44 +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 8b5550f789 Merge branch 'feat/do-remote-wipe' into feat/desktop-offline 2026-11-03 16:51:35 +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 791570772c test: open the sign-in note spec with the suite's browser settings 2026-11-03 16:15:16 +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 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 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
Linus Rath 436703b0f7 fix: keep mail encrypted in the browser's private files during an export
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.
2026-11-03 15:40:54 +01:00
Linus Rath a52a39f9e6 fix: keep one bad event from stopping reminders, offer Join for web links only, and keep summaries until they are seen 2026-11-03 15:40:10 +01:00
Linus Rath fbda1e3401 fix: keep the Docker build within Node's default heap #1510 2026-11-03 15:40:09 +01:00
Linus Rath b94bf27b30 test: check that a locked main window renews no token and answers nothing 2026-11-03 15:36:06 +01:00
Linus Rath 88cd1e4cc4 fix: ask about a forgotten PIN on the lock screen itself, where it can be seen 2026-11-03 15:18:58 +01:00
Linus Rath 940a572844 feat: hide an account's mail when its login is refused, until the new password or 30 days
A refused login used to delete the account's data, and with it everyone's
S/MIME and OpenPGP keys, also with offline off. The usual cause is a password
changed on another device, which Stalwart 0.16 and 1.0 both answer with
`invalid_grant`, so that lost unsent mail for little protection.

- A refused login signs out at once and hides the data. A new password brings
  it back, unsent mail included. Otherwise it goes after 30 days. The sign-in
  page gives a count and a date. The 24-hour timer is gone.
- `organisation.devices.wipeOnRefusedLogin`, off by default, asks for deletion
  at once, of that account's own data only.
- The session limit no longer counts as a refusal on any route. It hides the
  data and says nothing.
- A wipe runs under a Web Lock, tells other windows when it starts and ends,
  and queues its report before deleting anything. It reports only once the
  deletion worked and no plugin database is left.
- More goes with a wipe: Pro tabs, search history, Files and WebDAV state,
  templates, pop-out drafts, shown notifications, push state, and Web Push
  with the last account.
- The unsent count is per account, and unknown rather than 0 when the store
  can't say. A new row is checked against the account that asked, and a
  sign-out drops its row.

Existing tests changed because the rule did: `auth-store-keep-local.test.ts`
expected a wipe on `invalid_grant` and at the session limit, and
`device-wipe.test.ts` expected shared keys to go on a refused login.
2026-11-03 15:11:47 +01:00
Linus Rath d1227bfac6 fix: log a device wipe only when its row changes, and keep rows from piling up 2026-11-03 15:11:44 +01:00
Linus Rath 159bd8becc Merge branch 'feat/do-os-integration' into feat/do-reminders
# Conflicts:
#	components/desktop/desktop-os-host.tsx
#	lib/desktop/os-commands.ts
#	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 14:47:37 +01:00
Linus Rath 2836cb6466 Merge branch 'feat/desktop-offline' into feat/do-reminders
# Conflicts:
#	lib/desktop/types.ts
#	lib/offline/offline-mode.ts
#	lib/store/areas.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 14:46:00 +01:00
Linus Rath 306b249c07 chore: merge the offline and desktop base into the app lock branch 2026-11-03 14:40:25 +01:00
Linus Rath 4e86e8189a fix: stop automatic SSO towards a server off the device's list and say why in OAuth-only mode 2026-11-03 14:32:14 +01:00
Linus Rath bcd97d0d6e feat: merge offline contacts and files 2026-11-03 14:23:36 +01:00
Linus Rath 3cefd91dab fix: close the keystore bridge type the policies merge left open 2026-11-03 14:23:34 +01:00
Linus Rath 54852df738 test: stop a pinned download at the organisation limit when the size is missing 2026-11-03 14:12:19 +01:00
Linus Rath 7aac946a9d feat: merge managed deployment policies and enforcement
# 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 14:08:20 +01:00
Linus Rath 7173178ded fix: name the product on the lock screen, not the default app name 2026-11-03 14:04:32 +01:00
Linus Rath bd7d12f801 Merge branch 'feat/desktop-offline' into feat/do-remote-wipe
# Conflicts:
#	components/offline/offline-settings.tsx
#	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 13:58:22 +01:00