Commit Graph
567 Commits
Author SHA1 Message Date
Linus Rath ceaceb7f2a Merge branch 'feat/desktop-offline' into feat/do-ux-compose-fixes
# Conflicts:
#	components/email/email-composer.tsx
#	components/email/email-viewer.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-04 09:20:50 +01:00
Linus Rath 3841bdd630 Merge branch 'feat/do-drop-overlay' into feat/desktop-offline
# Conflicts:
#	desktop-policies/de-DE/Bulwark.adml
#	desktop-policies/en-US/Bulwark.adml
2026-11-04 08:37:26 +01:00
Linus Rath 2f31b435a2 Merge branch 'feat/desktop-offline' into feat/do-menu-fixes
# Conflicts:
#	components/desktop/desktop-host.tsx
2026-11-04 07:57:50 +01:00
Linus Rath b8646eefc4 Merge branch 'feat/desktop-offline' into feat/do-menu-fixes 2026-11-04 07:11:07 +01:00
Linus Rath 13272ad85d fix: move the taskbar flash and quiet hours to Settings > Notifications
The macOS bounce test renders the Notifications part, where the setting now is.
2026-11-04 06:56:28 +01:00
Linus Rath 364fc516dc fix: hide the browser's link handlers in the desktop app and call its window settings App windows 2026-11-04 06:29:25 +01:00
Linus Rath 80321e21a2 fix: explain replying from the received address in plain words
The admin-locks test finds the row by its label key, which moved to the
compose_words namespace with the new description.
2026-11-04 06:26:28 +01:00
Linus Rath 2f46eb6b46 Merge branch 'feat/do-menu-fixes' into feat/do-drop-overlay 2026-11-04 06:20:02 +01:00
Linus Rath a80c44b776 Merge branch 'feat/desktop-offline' into feat/do-drop-overlay 2026-11-04 06:19:39 +01:00
Linus Rath b425f4b211 feat: turn undo send on by default with 5 seconds
The policy-defaults test now expects the new build default of 5 seconds
for an invalid admin value, and the settings-search tests look for the
row under its new label "Undo send".
2026-11-04 06:18:29 +01:00
Linus Rath 1cf03ff0e4 feat: add a developer tools switch to the desktop app's troubleshooting settings 2026-11-04 05:38:59 +01:00
Linus Rath ff0e16c755 feat: show the desktop app's menu bar as a row at the top on Alt, or always 2026-11-04 05:20:02 +01:00
Linus Rath f9503570ca Merge branch 'feat/desktop-offline' into feat/do-key-fixes
# Conflicts:
#	components/settings/app-lock-settings.tsx
#	stores/auth-store.ts
2026-11-04 04:16:14 +01:00
Linus Rath 88d64b7f95 fix: protect the keys to the mail on this device for real
The review found the store key readable from a copy of the disk. The
desktop app only added its OS layer with the lock on, the record from
before a lock stayed in LevelDB and opened the mail with no PIN, any
frame could unwrap the PIN record and guess at it, a lock left the key
in the renderer's memory, and a store waiting after a refused login went
to whoever signed in next under the same username@host.

- The desktop app wraps the store key with safeStorage, lock or no lock.
  A record without the layer gets it at its next open. A refusal is said
  out loud, and only a computer with no key store goes without, which
  settings say. So do they for a browser, which has no such layer.
- A record that gained layers, and every record after a changed PIN, asks
  for a new key: `sqlite3_backup` into a new file under a new key while
  the store keeps working, one write to the record, then the old file
  goes. A changed PIN makes a new lock key, since the old PIN's record
  stays on disk too. The passkey and OS unlock go with the old key.
- `wrap` and `unwrap` come through `claim()`, and the key under the PIN
  takes a secret from main, one counted trip per guess.
- A lock on the desktop ends the renderer rather than reload in it.
- A sign-in compares the waiting store's JMAP account and server first,
  and wipes on a mismatch. The account id keeps its form: every store,
  setting and server record of an account is keyed by it.
- The 30 days also run in an app that stays open.

Changes an existing assertion: turning the lock off now keeps the
safeStorage layer, by decision, so that test expects it to stay.
2026-11-04 04:15:31 +01:00
Linus Rath be8aba2ae7 Merge remote-tracking branch 'origin/main' into feat/desktop-offline
# Conflicts:
#	stores/auth-store.ts
2026-11-04 03:42:18 +01:00
Linus Rath 09d6bff12a fix: say push isn't available in the demo instead of offering a switch that fails 2026-11-04 03:13:58 +01:00
Linus Rath 8b4fb72357 fix: say in plain words why push could not be turned on or off 2026-11-04 03:13:57 +01:00
Linus Rath 97c0bd633a Merge branch 'feat/desktop-offline' into feat/do-edit-attachments
# Conflicts:
#	lib/desktop/languages.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-04 02:40:47 +01:00
Linus Rath 71aaadef50 fix: run plugins' compose check in quick compose, keep a composer with one owner, and bring it back after a crash
The first version of quick compose skipped `onBeforeCompose`, a composer
moved back could end up open in both windows, a reply moved back under
Pro flagged the selected message, and a crashed pop-out came back blank.

- A new message in a pop-out runs `onBeforeCompose` with the same result
  as the main window: a plugin that stops it closes the window.
- The way back has two steps, so a pop-out that gave up can't leave a
  second owner. A handoff behind a plugin's gate makes it say busy.
- A Pro tab made from a composer moved back answers its own message.
- Each save writes `draft`, `account` and `owner` into the pop-out's
  address, which a reload after a crash starts from. What was typed since
  the last save, up to the 2 s autosave delay, is still lost.
- The recorder refuses keys that type a character on this keyboard, and
  what the shell refuses from a page, and says why.
2026-11-04 01:31:43 +01:00
Linus Rath e74248b37b feat: edit attachments in their own program from the desktop app and take the saved file back into mail 2026-11-03 23:37:40 +01:00
Linus Rath 3da18cc2c0 feat: set the quick compose keys in the desktop settings and move a composer back into the main window 2026-11-03 23:25:10 +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 423fe28b0b Merge branch 'main' into feat/do-ci-fix 2026-11-03 19:50:46 +01:00
Linus Rath 4015bbad76 test: expect the hidden settings row without a plugin, since the desktop app shows it for the window controls 2026-11-03 19:49:56 +01:00
Linus Rath 529d334496 Merge branch 'fix/ui-bug-hunt' 2026-11-03 19:44:46 +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 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 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 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 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 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 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 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 3687363d6e fix: stop Back doing nothing in Settings after the window crosses the phone width several times 2026-11-03 15:12:06 +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 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 fb3c82eed5 feat: lock the app with a PIN, a passkey or Windows Hello, with mail out of memory and the local store closed
The lock is off by default, and an OS policy can require it and set the
timeout. It locks after a time without use counted across all windows and
tabs, on OS screen lock in the desktop app, and from Lock now.

Locking saves open composers, stops the outbox at its next message, closes
the store and stops its worker with every key, then reloads the page, so
nothing of the old page stays in reach. The new page holds the app back at a
Suspense boundary until someone unlocks. Pop-outs get no session while
locked, push notifications name nobody, and subjects leave plain storage.

One random lock key wraps each account's key as a layer. The PIN wraps the
lock key with PBKDF2 at 600,000 iterations, with a wait after wrong tries
that a reload doesn't reset. A passkey with PRF and the desktop's OS storage
wrap it too. Details and limits are in the decisions file.
2026-11-03 13:08:37 +01:00
Linus Rath 426078e6ef Merge branch 'feat/desktop-offline' into feat/do-native-ui 2026-11-03 12:26:59 +01:00
Linus Rath 2edd6b5ea7 fix: hold forced policies against spoofed plugins, recommended values and stopped servers
A forced value could be got around from inside the app. Only a forced
value may now decide what's allowed: the keys that do are forced-only,
so a recommended one no longer acts as forced. With a plugin list a
package can name any id, so uploads are refused and only managed
plugins can match. A server taken off AllowedJmapServers stops its
accounts without signing out: client, push and sync stop, the switcher
drops them, restore and refresh answer 403 with the cookie kept, and
listing it again resumes with no sign-in. OAuth and SSO starts check
the list too.

- `policy.onWipeOfflineData` is gone from the bridge: main runs the wipe
- `KeystoreAllowedOrigins` added, and `POLICY_CONSUMERS` names who acts
  on each key or who still owes it
- a Bulwark server's name must be plain after parsing, a wildcard in the
  auth lists needs a domain of two labels
2026-11-03 12:06:21 +01:00
Linus Rath aebce87776 Merge branch 'feat/do-native-ui' into feat/do-app-lock 2026-11-03 12:01:57 +01:00
Linus Rath 9719bc5fd1 Merge branch 'feat/do-os-integration' into feat/do-reminders
# 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 11:57:45 +01:00
Linus Rath 1d4587f7ea fix: stop a stuck certificate call holding mail rendering, and let a server's certificate access be taken back 2026-11-03 11:31:14 +01:00
Linus Rath b2931ced3e feat: bring desktop windows back at the next start, open pop-outs from a warm spare and ask natively before discarding a draft 2026-11-03 10:44:02 +01:00
Linus Rath a9b6a05841 feat: enforce the desktop app's OS policies for plugins, mail servers and offline mail 2026-11-03 10:15:15 +01:00
Linus Rath 947743cc9e feat: connect the desktop app's tray, badge, notifications, files and settings to the webmail 2026-11-03 10:13:06 +01:00
Linus Rath 391edb8f92 feat: show the desktop app's updates and channel in the general settings 2026-11-03 09:56:16 +01:00
Linus Rath 503242ac4c fix: cut a plugin's name in the settings list at its end in right-to-left languages 2026-11-03 09:33:12 +01:00