Commit Graph
1035 Commits
Author SHA1 Message Date
Linus Rath 1b7b689662 Merge branch 'feat/desktop-offline' into feat/do-ux-p2-mail-fix
# Conflicts:
#	components/ui/first-start-notice.tsx
2026-11-04 13:46:47 +01:00
Linus Rath 19034489a6 Merge branch 'feat/desktop-offline' into feat/do-ux-p2-mail-fix 2026-11-04 12:57:11 +01:00
Linus Rath b1ae1cc8c5 Merge branch 'feat/desktop-offline' into feat/do-ux-p2-settings-fix
# Conflicts:
#	components/calendar/event-modal.tsx
#	components/calendar/task-list-view.tsx
#	components/calendar/task-modal.tsx
#	components/email/calendar-invitation-banner.tsx
2026-11-04 12:56:40 +01:00
Linus Rath badbd616df Merge branch 'feat/do-ux-p2-settings-search' into feat/do-ux-p2-settings-fix 2026-11-04 12:39:59 +01:00
Linus Rath 3a2be9a7d4 fix: edit the name recipients see on the Account page and stop cutting account addresses
Settings > Account had a "Display name" that edited Stalwart's account
description, while mail went out with the identity's name, so changing
it changed nothing people saw. The page now shows "Your name", the
primary address's name, and saves it to that identity. The account
switcher and avatars take the same name; the principal description is
only used for an account without an identity name. The address editor
calls the field "Your name" too, and "identities" are "addresses" in
the Account section and its dialog, said once instead of twice.

Logged-in accounts show the server only for an account in trouble or
one sharing its address with another, with no drag hint or arrows for a
single account. The switcher wraps addresses instead of cutting them and
has no remove button per row; an account you're not in is removed from
Settings > Account.

Changed tests: auth-store-display-name now expects the identity name to
win over the principal; account-switcher-remove expects no remove
button on the rows; settings-editors-unsaved uses the renamed "Add
address" button and the "Your name" label, which has no placeholder.
2026-11-04 12:38:38 +01:00
Linus Rath 4b426e1469 fix: find the desktop app's own settings in search, and drop browser words from its pages
Settings search knew none of the desktop app's rows, short words matched
a dozen descriptions, and the app's Notifications, Privacy and Backup
pages talked about websites, browsers and Safari.

- Search indexes the app's rows when its bridge is there (tray, start at
  sign-in, menu bar, quiet hours, taskbar, default mail app, updates,
  developer tools, usage info, crash reports) with translated synonyms,
  and the browser-only push rows only in a browser. New synonyms for
  threads, block images, dark mode, timezone, reading pane, week start,
  empty trash and quiet hours. A single word of up to five letters looks
  only in names and synonyms, so "about" finds About.
- In the app, Notifications has no push relay section; one row says the
  app tells about new mail while it runs, with a link to General. Its
  taskbar and quiet hours section is "On this computer". Privacy has no
  relay link there, Backup says "other devices".
- In a browser that can't push, two sentences, and the home screen hint
  only on iPhone and iPad.
- Several accounts get one notification switch each instead of a
  checklist with "Enable for selected" and "Disable for selected".
- Developer tools show only with advanced settings on.
- "Two-step sign-in" heads its section, "Pictures from company websites"
  replaces "Sender favicons", the Privacy relay link no longer points to
  a hidden choice, Offline warns about disk encryption once, and the
  reset confirm says what stays and whether other devices change. A
  reset keeps the choice to sync settings.

Changed assertions: account-security-signin looks for the heading by its
new name, settings-search expects the renamed rows, multi-account-push
tests the switches that replace the checklist.
2026-11-04 12:38:35 +01:00
Linus Rath 90e959a092 fix: show a sent reply in the open conversation at once, keep it out of Sent and Scheduled during Undo, and bring an undone reply back where it was written 2026-11-04 12:35:50 +01:00
Linus Rath be57f23bf6 fix: say when merging contacts left a card behind, and don't let Undo copy it 2026-11-04 11:36:52 +01:00
Linus Rath a415db9bac Merge branch 'feat/desktop-offline' into feat/do-ux-pim-fixes
# Conflicts:
#	stores/settings-store.ts
2026-11-04 11:10:15 +01:00
Linus Rath c08e9e56fb Merge branch 'feat/do-ux-pim-files' into feat/do-ux-pim-fixes 2026-11-04 11:09:45 +01:00
Linus Rath b04292ff4d feat: merge duplicate contacts and list possible duplicates
The contact menus offered "Duplicate", which made more copies. They now
offer "Merge with…", the selection bar has "Merge…" for two or more
contacts, and a "Possible duplicates (n)" line over the list opens a view
that groups cards with the same name, email address or phone. The test
that expected "Duplicate" in both menus now expects "Merge with…", and
the test for a failed duplicate became tests for merging.
2026-11-04 11:02:52 +01:00
Linus Rath e5dc1dd247 feat: ask Replace, Keep both or Skip when an upload's name exists in Files
An upload over a name that is taken in the target folder used to add
"name (1)" without a word. Every upload path (picker, drag and drop, the
drop overlay, folder upload) now asks per name, with "Do this for all n
conflicts" when there are several:
- Replace gives the existing node the new blob, so it keeps its id,
  shares and links; for a folder it merges into the existing one and
  replaces files of the same name.
- Keep both numbers the new one, as before.
- Skip leaves it out; Cancel stops the whole upload.

Also in Files:
- A file with no preview and no office editor opens a pane with its
  icon, size and Download instead of downloading at once, and in the
  desktop app also Open in default app (the attachment Edit path). The
  item menu gets Open for such files, and Open in default app in the
  desktop app.
- Enter opens the row Tab put the focus on.
- After sharing with someone new, a toast offers Send a message, which
  opens the composer to them with a link to the file.
- The empty folder's buttons read Upload files... and New folder..., as
  the menus do.
2026-11-04 11:01:25 +01:00
Linus Rath 0c022459d2 feat: import contacts from Outlook and Google CSV files into the address book you choose 2026-11-04 10:57:56 +01:00
Linus Rath d783981373 Merge branch 'feat/desktop-offline' into feat/do-ux-pim-fixes
The settings pass hid each calendar's CalDAV address behind "Use in
other apps"; pim-26 lists the address once per account. The list now
waits behind one "Use in other apps" button, and the wording test
clicks the new Edit… button instead of the old pencil.
2026-11-04 10:09:03 +01:00
Linus Rath 989385f4f6 Merge branch 'feat/desktop-offline' into feat/do-ux-compose-fixes
# Conflicts:
#	components/email/email-viewer.tsx
#	components/settings/accessibility-settings.tsx
#	lib/__tests__/settings-search.test.ts
#	lib/settings-search.ts
2026-11-04 09:54:25 +01:00
Linus Rath aa62855759 Merge branch 'feat/do-ux-settings-fixes' into feat/desktop-offline
# Conflicts:
#	components/layout/__tests__/account-switcher-mountain-view.test.tsx
#	components/layout/account-switcher.tsx
#	components/settings/accessibility-settings.tsx
#	components/settings/reading-settings.tsx
2026-11-04 09:44:51 +01:00
Linus Rath 4a764150b2 Merge branch 'feat/desktop-offline' into feat/do-ux-settings-fixes 2026-11-04 09:22:44 +01:00
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 fcc9fc0cc0 Merge branch 'feat/desktop-offline' into feat/do-ux-mail-fixes
# Conflicts:
#	components/email/email-viewer.tsx
#	components/mail/mail-app.tsx
#	components/offline/offline-host.tsx
#	components/ui/welcome-banner.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:11:09 +01:00
Linus Rath 044647ff34 fix: keep the desktop layout in a half-screen window and at 150 % zoom
A window snapped to half the screen (640-800 px) or a 1366x768 laptop
at 150 % (911 px) got the phone and tablet layout: bottom tab bar,
folders in a drawer, Settings as a phone list with a Back arrow. The
layout switched at the 64rem (1024 px) breakpoint.

Now the phone layout starts below 600 px. From 600 to 1024 px the app
is a compact desktop: app rail at the side, folder sidebar folded to
its icons (the existing collapsed state, opened per session with its
toggle), Settings with its two panes and a narrower page list. Below
840 px the split layout opens a message in the list's place, as the
focused layout does. This holds in the browser and the desktop app,
in the Pro shell (which now runs from 600 px) and under Mountain View.

- `hooks/use-media-query.ts`: classifyWidth, PHONE_BELOW 600,
  FULL_DESKTOP 1024; the tablet class (`isTablet`) is gone, the store
  has `isCompact` instead. Plugins still see "tablet" for it.
- `app/globals.css`: a `desk:` breakpoint at 37.5rem for the layout
  classes, and a `tap:` variant for 44px rows on phones and touch
  screens below 1024 px (was every window below 1024 px).
- Touch screens below 1024 px keep swiping list rows (was phones only).
- The boot skeletons, boot script, mail app, sidebar, reader, admin
  shell and calendar, contacts and files follow the new widths.

Tests changed with the behaviour: the tablet cases in pro-gate,
mail-app-mv-composer-move, mail-app-compose-handoff,
mail-app-thread-expand-mark-read, mobile-tour-end and
thread-list-item-columns now run on a phone or a compact desktop, as
those widths no longer exist as a tablet. mail-app-short-window-tab-bar
is replaced by mail-app-compact-layout: a phone on its side is a
compact desktop with the rail at the side, so the short-window tab bar
rule is gone. Class and media-query assertions follow desk:/tap: and
37.5rem; the 32px-text test uses 1100 px, since 1440 px at 32px is now
a compact desktop.
2026-11-04 09:07:06 +01:00
Linus Rath ceaa81cd9e Merge branch 'feat/do-uxs-plugins' into feat/do-ux-settings-fixes 2026-11-04 08:34:57 +01:00
Linus Rath 2429556b93 feat: show every signed-in account's calendars together
With two accounts signed in, Calendar showed only the active one's
calendars. Now, as in the Pro interface, the sidebar lists each
account's calendars under its name, each with its own checkbox, ticked
by default, and the grid shows the ticked ones together. Creating,
editing, moving, deleting and answering an event of the other account
go through that account; tasks of all accounts are listed and change in
their own account.

- a calendar one account sees as shared and the other owns shows once
- a load or push that names one account keeps the merged list
- an event made in the other account's calendar no longer takes the id
  of the active account's event with the same raw id
- a change to an event the store no longer holds still goes to the
  account its id names
2026-11-04 08:08:21 +01:00
Linus Rath 19e5ca5077 fix: list the tasks of task-only lists and turn tasks on by default
The tasks view filtered tasks by the grid's calendar selection, which
never holds a task-only list such as "To-do". Those tasks never showed
while the sidebar counted them, and a task moved into To-do vanished on
save. The tasks view now has its own task lists in the sidebar, ticked
by default, and the grid's selection is left alone.

- tasks are on by default; an "off" the user never chose turns on, one
  they chose stays
- new tasks go to the first task-only list
- "Add a task…" reads a date at the end of the title (tomorrow, Friday,
  12.11.) into the due date and shows it as a chip first
- the task status says "Not started" and "In progress"

filterTasksForList now takes the unticked task lists instead of the
selected calendars, so task-plugin-surfaces passes the unticked list and
task-list-dates no longer passes selectedCalendarIds.
2026-11-04 08:02:27 +01:00
Linus Rath ce34617147 feat: turn plugins an admin installs on for every user, who can turn them off
The #1327 store test now serves its optional plugin with defaultOn false:
it checks that every plugin is listed when IndexedDB fails, and a plugin
the admin didn't force is on by default now.
2026-11-04 08:01:38 +01:00
Linus Rath 17bf3c4e6f Merge branch 'feat/do-ux-compose-send' into feat/do-ux-compose-fixes 2026-11-04 07:41:26 +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 5c3a12f91c feat: use the plain wording for everyone and drop the plain language switch
The plain-language test asserted that the normal strings show while the switch is off and that changing the switch swaps them live. With no switch left, it now asserts the plain strings show from the first render and through a language change.
2026-11-04 07:04:42 +01:00
Linus Rath e8d8486b85 fix: cut a row's right-click menu to the usual actions, with Restore in Trash and read and unread for mixed selections 2026-11-04 07:00:54 +01:00
Linus Rath 317f3b40dd fix: suggest recipients whose name starts with what you type first 2026-11-04 06:42:47 +01:00
Linus Rath 781013cc44 feat: close the composer as soon as Send is pressed and show Sending, then Sent with View 2026-11-04 06:41:51 +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 60a97303c2 fix: open the folder when its unread count is clicked, and search all folders again on a new search
Two sidebar tests asserted the old unread-count button (it filtered the
folder to unread without opening it, in a 24px target); they now check
that the count is plain text and opens the folder.
2026-11-04 06:17:05 +01:00
Linus Rath 9ce7ac8172 feat: show unread per account in the switcher and count every account on the app badge and tray
The default layout's switcher listed accounts without their unread, so an
existing test asserted that (account-switcher-mountain-view, 'unread-free');
it now expects each account's count. The idle-outside-Pro hook test now
uses one account, since a second connected account loads in every layout.
2026-11-04 06:06:37 +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 bce0c0b744 Merge branch 'feat/desktop-offline' into feat/do-keystore-fixes 2026-11-04 03:52:09 +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 d3308e7729 Merge branch 'feat/desktop-offline' into feat/do-keystore-fixes
# Conflicts:
#	lib/desktop/__tests__/policy-schema.test.ts
2026-11-04 03:31:22 +01:00
Linus Rath 9b61b79d5c fix: sign only CMS signed attributes through api.keystore, and ask a user about a managed plugin's keystore
The shell now takes a message's signed attributes where it took a digest,
so a page can't get a login or TLS digest signed. A quiet option lets a
plugin answer from what's allowed, for anything the user didn't ask for.

An admin's install no longer covers the certificates on someone's own
computer: a managed plugin gets `keystore` when that person says yes, also
when an update adds it, and it's forgotten once everyone has signed out.

Changed test assertions: the fixture plugin needs a grant now, as managed
alone is no longer enough, and the bridge gets an options argument.
2026-11-04 03:23:45 +01:00
Linus Rath f0e874103d fix: take back certificate grants when a session ends without a sign-out 2026-11-04 03:23:45 +01:00
Linus Rath 484ce34f5d Merge branch 'feat/do-browser-signin' into feat/desktop-offline 2026-11-04 03:19:32 +01:00
Linus Rath 8cb218ce3c fix: apply email notifications off and do not disturb per login, not to every login's mail 2026-11-04 03:13:59 +01:00
Linus Rath f0517ecd08 test: type the sign-out push test's fake client 2026-11-04 03:13:59 +01:00
Linus Rath 248f9c261b fix: take down push for logins with no connection when signing out of everything 2026-11-04 03:13:59 +01:00
Linus Rath 80084457be fix: end a removed login's push before its client disconnects 2026-11-04 03:13:58 +01:00
Linus Rath a6ef818149 fix: end a login's push registration when it signs out, is removed or wiped 2026-11-04 03:13:57 +01:00
Linus Rath fc6a209727 fix: keep push notifications off screen with email notifications off or do not disturb on 2026-11-04 03:13:56 +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 ab21bf2df3 fix: keep a desktop sign-in in the browser's page until the user says yes
The hand-off parked a password before its question, for up to five
minutes, and on a shared computer another local user could ask for
someone's sign-in with a link of their own. Now nothing leaves the page
before the yes, and the instance has a switch for such machines.

- `/check` tests a password and keeps nothing, `/park` runs on the yes and
  hands over tokens wherever the server allows, a password as a last
  resort
- `/discard` and a timer take out what SSO has to park first, and a no
  revokes the refresh token, and the shared store's TTL is the real expiry
- `DESKTOP_BROWSER_SIGN_IN`, default on, closes the routes, the page's
  hand-off and the app's button; the question says which device asked
- tokens from a typed password stay a password account under the name
  typed (#1432)
- redeem refuses a server of the user's own when the device's rule says
  no
- the park budget is reserved before going upstream, and only failures
  count
- "Not now" ends the flow in the app at once, and the question names the
  account

`desktop-handoff-routes.test.ts` now tests the two-step flow against the
real budgets. `sso-start-route.test.ts`'s config mock answers the new
setting, and the SSO test's expected answer has the account in it.
2026-11-04 02:40:37 +01:00
Linus Rath d31d0af560 fix: say what an open program still holds, keep forwarded parts under their rules, and let main decide what a sent draft leaves 2026-11-04 02:36:48 +01:00