Commit Graph
1031 Commits
Author SHA1 Message Date
Linus Rath 29c74c3b77 feat: sign in with an access token 2026-10-01 00:24:47 +02:00
Linus Rath 2d2b0753be Merge pull request #1132 from lucamzanon/perf/multi-account-startup
perf: stop the startup request storm with many logins
2026-09-30 20:33:32 +02:00
Linus Rath ca7d5e22df fix: keep a late notification link from pulling you out of what you opened #1131
A notification link now waits up to 20 s for its login to reconnect.
If the user opened another folder or message, switched account or left
Mail in the meantime, the late link used to switch account anyway and
take them away from it. It now stays put.

When the account switch was overtaken by another one or failed, a
folder link was resolved against whichever folders were on screen, so
an Inbox link could open the wrong account's Inbox. It now stops there.
2026-09-30 20:33:08 +02:00
Linus Rath efe44e09cc Merge pull request #1131 from lucamzanon/fix/deep-link-late-login
fix: let a notification link wait for a login that is still reconnecting
2026-09-30 20:32:44 +02:00
Linus Rath 692be6aa9d Merge pull request #1095 from lucletoffe/fix/calendar-location-links
fix(calendar): surface the meeting link and open the location in maps
2026-09-30 20:32:44 +02:00
Linus Rath ac09146054 feat: turn pasted dash and numbered lines into lists in the composer 2026-09-30 20:00:01 +02:00
Linus Rath bfc8e0e52e fix: show only that folder's unread mail when its unread count is clicked
Clicking a folder's unread count turns on the unread filter and runs
the search. Since the search scope stopped following the open folder
(#788) its default is "all folders", and since #1082 that fans out over
every shared account too, so the Inbox badge listed unread mail from
every folder of every account the login reaches.

The badge now scopes the search to its folder. That scope moves with
the open folder, the way the search itself carries across folders
(#553); picking a scope in the dropdown or clearing the filters stops
it. Clicking the badge from a tag view or the Scheduled list now also
leaves that view, as clicking the folder does.
2026-09-30 19:49:13 +02:00
Linus Rath c081a51842 fix: keep blank lines when pasting plain text into the composer 2026-09-30 19:46:05 +02:00
lucamzanon a578150c2d perf(unified): fetch each login's folders once, all logins at once
The unified scope is rebuilt on every connection while a browser restores
its logins, on every background push, and on every unified browse,
load-more and search - and each build asked every login for its folders
again, one login after the other. With ten logins that was ten full
Mailbox/get per build and well over a hundred in the first seconds, and the
slowest build waited on the sum of every login's round trip.

Fetch the logins in parallel, and fetch each login's list once: a build
shares a fetch that is already in flight and reuses a finished one until
that login reports a change - the push handlers invalidate the login they
hear from, so a background push now costs one Mailbox/get instead of one
per login. A 60 s limit backstops a change whose push was lost.

A reused list is never written back into `accountMailboxes`: optimistic
updates land there directly, and publishing an older list over them would
snap a just-changed counter back.
2026-09-30 19:03:34 +02:00
lucamzanon 2d5756bff7 perf(push): reconcile push bindings instead of rebuilding them
The push effect re-runs every time a login connects, which while a browser
with many logins restores means once per account. Each run closed every
client's push and set it up again: the live streams were torn down and
reopened and every slow poll re-primed its baseline with a full state read
(Mailbox, Email, Calendar, CalendarEvent, SieveScript) - quadratic in the
number of accounts. With ten logins that was 21 stream openings and about
180 state-poll methods in the first seconds.

Reconcile instead: bind the clients that are new, unbind the ones that are
gone, and on an account switch re-bind only the two logins whose role
changed, handing the live stream to the newly active one when it has
none. Handlers are read through a ref at event time, so a binding never has
to be re-created because a callback changed identity.

The reconciliation is a pure function (lib/push-bindings.ts) with its own
tests; the effect only supplies the callbacks.
2026-09-30 19:03:34 +02:00
lucamzanon 1d82c59469 fix: resolve a folder link against the mailbox it switched to
Once a link had waited for its login and switched to it, the folder was
looked up at once, in a store that still held the previous mailbox's
folders or none at all: an inbox link opened on a mailbox that was just
switched to reported "this folder is no longer available". Wait for the
new mailbox's folder list to arrive before resolving the link against it.
2026-09-30 19:01:24 +02:00
lucamzanon 1c5dbbcd0b fix: let a link wait for a mailbox that is still reconnecting
A notification tapped with the app closed reopens it on a link that names
its mailbox (`?slot=`). The browser reconnects its logins one at a time,
so that mailbox is often not connected yet when the link is read: with ten
logins, one on the Gmail bridge came back at 3.3 s and the link was judged
at 1.8 s. It was declared unavailable - "sign in to that account to open
this link" - and the app stayed on whichever mailbox was active.

Wait for the login instead, and give up only when it is not one of this
browser's logins, its restore fails, or 20 s go by.
2026-09-30 19:01:14 +02:00
Linus Rath 7f5728fea2 Merge pull request #1101 from dealerweb/fix/wopi-base-path
fix: send the WOPI status and launch requests under the base path
2026-09-30 16:46:11 +02:00
Linus Rath 13b2ea3d19 fix: keep a previewed SVG attachment from running script in the webmail origin 2026-09-30 15:12:19 +02:00
Linus Rath d0a1d0183b Merge pull request #1127 from dealerweb/fix/preview-declared-type
fix: preview attachments whose declared type the browser cannot show
2026-09-30 14:57:33 +02:00
Linus Rath e127f5d24b fix: reuse the translated saving label on the event Save button #921 2026-09-30 14:23:33 +02:00
Linus Rath 7282adbb06 Merge pull request #921 from texchi2/fix/calendar-save-in-flight
fix(calendar): show in-flight state on Save, and surface the silent length-guard rejection
2026-09-30 14:23:09 +02:00
Linus Rath fa789a6d9f Merge pull request #748 from lucletoffe/fix/calendar-blank-cn-and-description
fix(calendar): stop writing blank participant names and empty descriptions
2026-09-30 14:21:20 +02:00
dealerweb 1512898cdb fix: preview attachments whose declared type the browser cannot show
The preview hands the attachment's blob to the browser's own viewer,
which picks the viewer from the blob's type and downloads anything it
cannot show - under the blob's UUID. It only corrected the exact
application/octet-stream declaration from the file name. Other
declarations kept the preview from working: IBM i (AS/400) systems send
every attachment as application/x-as400attachment, some senders use
application/x-pdf or force-download, and the octet-stream check was
case-sensitive.

Move the decision into previewBlobType(): a PDF, image, audio or video
preview takes the first type its viewer can show - declared, then
served, then the one the file name implies. Other previews keep the
declared type unless it is generic, and the name never yields a
script-capable type.
2026-09-30 14:20:20 +02:00
Linus Rath 1531dae7eb feat: create filter rules from the message right-click menu 2026-09-30 13:52:41 +02:00
Linus Rath 62e07608fd fix: stop the mail list hiding dividers between some rows 2026-09-30 00:21:21 +02:00
Linus Rath e6a565ee27 fix: bring the mail list back on tablet when the reading pane empties 2026-09-30 00:08:02 +02:00
Linus Rath 9d140fa762 Merge pull request #1052 from lucamzanon/feat/account-row-colours
feat: colour unified rows by account and give every tag a hue of its own
2026-09-29 23:30:24 +02:00
Linus Rath 308fb083a4 Merge pull request #1122 from sanitz/fix/week-allday-visible-week
fix(calendar): size the week view all-day area from the visible week
2026-09-29 23:24:42 +02:00
Linus Rath affa999cd0 fix: report recipients the server rejects at send #1123 2026-09-29 20:49:40 +02:00
Linus Rath be0494cb5c fix: move mail to trash when emptying an ordinary folder 2026-09-29 20:35:09 +02:00
Linus Rath 324fb48312 fix: keep an opened mail in place in unread-first lists until another is opened 2026-09-29 20:28:39 +02:00
Linus Rath 3bccca715c feat: show the folder hierarchy in the search folder dropdown 2026-09-29 20:15:14 +02:00
Linus Rath 3904af2cbb feat: search spam and trash themselves while they are open 2026-09-29 20:09:26 +02:00
Linus Rath 763732fdc9 feat: leave spam and trash out of search by default 2026-09-29 19:52:39 +02:00
Sebastian SanitzandClaude Opus 5.5 695cc7303f fix(calendar): add translations for the all-day expand toggle
The expand/collapse toggle referenced calendar.events.show_more and
show_less, which were missing from every locale (translations test failed),
and passed a defaultMessage option next-intl does not support. Add both keys
to all 27 locales, reusing each locale's existing email_composer.show_less
wording, and drop the unsupported option.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 00:08:15 +02:00
Sebastian SanitzandClaude Opus 5.5 ea6d0ee678 fix(calendar): size the week view all-day area from the visible week
The all-day/tasks area in the week view took its height from
(allDayRowCount + taskRowCount) across every loaded day. Since free
scrolling loads 37 to 180 days, one crowded day anywhere in that range
blew the header up to 250+ px for every week and pushed the time grid down;
tasks were also stacked below the global all-day row count, leaving a gap
on days without all-day events.

- Stack each day's tasks directly below that day's all-day events.
- Size the area from the days in view only: track the first visible day by
  date (so columns prepended while loading do not shift it) plus the number
  of visible columns (7 on desktop, by width on mobile).
- Cap the area at 3 rows with an expand/collapse toggle when a visible day
  has more entries.

Tests cover per-day task stacking, the 3-row cap and toggle, an empty week
next to a crowded one, and the area growing when scrolling into it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 23:42:30 +02:00
Linus Rath 0b3e47e3d2 fix: keep the composer body reachable in short reading panes #1114 2026-09-27 21:54:02 +02:00
Linus Rath aed0d27320 fix: stop the mail list flashing a reload right after the page opens 2026-09-27 18:21:24 +02:00
Linus Rath be3c4b0d99 feat: draw calendar events as solid blocks and toasts as plain cards 2026-09-27 12:45:19 +02:00
Linus Rath 9ab2035113 fix: centre the unread dot on the sender avatar 2026-09-27 11:28:06 +02:00
Linus Rath 3ed484df35 feat: offer the code from sign-in mail as a copy chip in the list and reader 2026-09-27 11:19:48 +02:00
Linus Rath e4ef2fa3d6 fix: keep an abandoned pairing re-auth from breaking the next sign-in
The Link Mobile App step-up marked the tab before asking the server to
start the provider round trip. When that request failed, or the user
backed out at the provider, the mark stayed, and the next sign-in's
callback in that tab went to the pairing route and failed. The mark is
now set only once the round trip starts, holds that round trip's state,
and the callback drops one that does not match.
2026-09-27 04:56:48 +02:00
Linus Rath 377c283d86 fix: say up front when the mobile app cannot use an http mail server
The app refuses a JMAP server over plain http (loopback aside), yet the
desktop issued a code, reported "Signed in on your phone" once it was
redeemed, and the phone failed. Linking now stops before the step-up
with an explanation.
2026-09-27 04:53:49 +02:00
Linus Rath 96e0e6b036 fix: keep the pairing QR on screen while the Security tab checks the server 2026-09-27 04:18:09 +02:00
Linus Rath 1446fd39dc fix: link the mobile app from every account with a sign-in of its own
"Link Mobile App" only appeared for OAuth sessions, its step-up could
only go through the identity provider (which failed without an OAuth
client and bounced password+TOTP users to the provider's login page),
and it handed the phone the desktop's own refresh token. Revoking that
token on sign-out, or a rotating provider, signed the phone out too; a
confidential client stopped the phone at its first token expiry; and an
identity provider on another host was refused by the app.

The step-up now mints the phone's own grant:

- Password and password+TOTP accounts confirm the current password (and
  code) in the dialog. The server uses it once to get an OAuth grant
  from Stalwart's structured login for the app's own client, retries
  with the webmail's client when the server insists on a registered one,
  and falls back to an app password ("Bulwark mobile (paired <date>)")
  or, on servers that offer neither, the verified password. Failed
  attempts are limited per account so the webmail's address cannot be
  banned by guessing through it.
- SSO accounts keep the provider round trip, now with `offline_access`
  where offered; its fresh tokens become the phone's grant instead of
  being thrown away, and must belong to the account being paired.
- The account and server always come from the slot's verified session
  context, never from the request.

Grants live 5 minutes server-side, codes 2 minutes, both single use; the
store is pinned on `globalThis`. Redeem answers `invalid_code`,
`expired_code` or `used_code`, and the desktop polls a separate status
id to replace the QR with "Signed in on your phone". A phone whose
client is confidential, or whose provider is on another host, renews
through `/api/auth/pair/token` with a sealed refresh token, so current
app builds work unchanged. Signing out discards a pending grant but no
longer affects a paired phone.

The dialog shows for every account (not in Lite), with a copyable
sign-in link, a TOTP field when needed, and the app's own "Scan a
sign-in code" wording.
2026-09-27 04:16:18 +02:00
Linus Rath 5ce8c89c51 fix: open and save files shared with you in the office editor #1094 2026-09-26 22:28:34 +02:00
Linus Rath 09e0e2cfb3 fix: show events you declined struck through in the calendar #1110 2026-09-26 21:32:08 +02:00
Linus Rath 078e9f9676 feat: show tasks with due dates in the calendar month view #1107 2026-09-26 20:47:13 +02:00
Linus Rath 7b25d3a95b fix: keep message list from jumping while attachment chips load 2026-09-26 20:24:23 +02:00
Linus Rath aa0075a8e1 fix: write only the changed keywords when tagging or pinning a message
Tagging and pinning replaced the message's whole keyword set with a
copy built from the list row. When the row was stale - the mail had
been read or answered on another device since - the write cleared
`$seen` or `$answered` again, and removed tags were written as `false`,
which a JMAP keyword map may not hold.

Keyword writes now go out as `keywords/<name>` patches for just the
keywords that change, removals as `null`, through the call that
reports a refused update. The local row gets the same patch.
2026-09-26 16:16:13 +02:00
Linus Rath 4858eb0915 fix: open a push notification's message in the account it came from
Clicking a new-mail notification opened `/mail/message/<id>` in
whichever account was active. With several accounts signed in, that
looked the id up in the wrong account: nothing opened, or a different
message did and was marked read.

The preview API now returns the cookie slot of the login that owns the
notified account, the service worker adds it to the permalink (or links
that account's Inbox for a group notification), and the app switches to
the login on that slot before opening the message.
2026-09-26 16:12:20 +02:00
Linus Rath efdc6a1dc3 fix: keep a reply draft in its thread when it is re-opened or undone
Drafts were saved without In-Reply-To and References, and a draft
re-opens in compose mode, where the composer has no original message to
rebuild them from. A reply that was saved and re-opened, or sent and
brought back with Undo, therefore went out as a new conversation.

`createDraft` now stores the threading headers, the composer saves them
with every draft of a reply, and re-opening a draft carries them into
the composer for later saves and the send.
2026-09-26 16:06:28 +02:00
Linus Rath 89ebb3a6ce fix: report a failed message-list read instead of an empty folder
`getEmails` and `getSomeEmails` caught every failure and answered with
an empty result. A refresh during a server hiccup therefore emptied the
message list, and a push delta whose follow-up read failed treated every
updated row as gone from the folder while still moving the sync state
past those changes, so the missed mail only came back on a full reload.

Both now throw. A refresh that fails keeps the list on screen, a delta
that cannot be read falls back to the full refresh, and the unified
views already record per-account failures instead of showing nothing.
2026-09-26 15:57:11 +02:00
Linus Rath d583443292 fix: keep an open draft on its own account across an account switch
The composer took its client, identities and active account live from
the stores. Switching accounts with a draft open moved it: autosave
created the draft in the other account, and replacing the previous
save sent that account a delete for the old draft's id, which there
names an unrelated message. The composer now stays on the account it
was opened in; the other account's identities and client are ignored
until it is closed.
2026-09-26 15:37:18 +02:00