Commit Graph
71 Commits
Author SHA1 Message Date
Linus Rath 2fbfa7a177 fix: give the focus back to the folder or message checkbox after closing a menu opened with Shift+F10 2026-10-28 09:57:32 +01:00
Linus Rath 0cd28097e6 Merge branch 'main' into ux/mail2 2026-10-28 08:16:51 +01:00
Linus Rath ed9fa2de45 feat: offer Undo after a move to another account and in lists that mix accounts, moving the messages back into their folders 2026-10-28 08:06:45 +01:00
Linus Rath 4b2aba430c fix: let Back close overlays the same way in calendar, contacts and files 2026-10-28 07:32:05 +01:00
Linus Rath 731ec15af4 Merge branch 'main' into ux/calendar2
# Conflicts:
#	hooks/use-close-on-back.ts
2026-10-28 07:21:42 +01:00
Linus Rath d87678a7ba feat: search events from the calendar toolbar, with / and a palette command 2026-10-28 07:17:16 +01:00
Linus Rath 1d5091f68b fix: close the event or task editor on browser back instead of leaving the calendar, asking first when it has changes 2026-10-28 06:39:15 +01:00
Linus Rath afd5d8263c fix: delete a contact category the same way in Settings and Contacts, with the same Undo toast 2026-10-28 06:16:10 +01:00
Linus Rath 5a898dcfd7 fix: let Escape close only the dialog, not also clear the selection behind it 2026-10-28 02:07:54 +01:00
Linus Rath 4c8d597f82 fix: keep Escape working in dialogs after a click inside or on the backdrop 2026-10-28 01:12:42 +01:00
Linus Rath d610312483 fix: update the open calendar and show reminders on time when events change elsewhere #1349 2026-10-26 20:35:52 +01:00
Linus Rath af536ac90a fix: keep the account and other query parameters of a mail link Lite replays after a 404 #1352 2026-10-26 20:01:10 +01:00
Linus Rath 3a225031bc fix: resize calendar events to the clock time the edge was dropped at on DST change days #1348 2026-10-26 20:01:07 +01:00
Linus Rath cf2c27157d Merge branch 'main' into feat/stalwart-1.0
# Conflicts:
#	components/contacts/contacts-app.tsx
#	components/email/__tests__/composer-discard.test.tsx
#	components/email/calendar-invitation-banner.tsx
#	components/email/email-composer.tsx
#	components/mail/mail-app.tsx
#	components/pro/pro-compose-tab-body.tsx
#	components/pro/pro-email-tab-body.tsx
#	components/ui/__tests__/avatar.test.tsx
#	components/ui/avatar.tsx
#	hooks/use-mailbox-drop.ts
#	lib/__tests__/calendar-alerts.test.ts
#	lib/__tests__/jmap-calendar-tasks.test.ts
#	lib/__tests__/jmap-send-threading.test.ts
#	lib/demo/demo-client.ts
#	lib/demo/fixtures/calendars.ts
#	lib/jmap/client-interface.ts
#	lib/jmap/client.ts
#	lib/web-push.ts
#	stores/__tests__/contact-store.test.ts
#	stores/__tests__/email-store-thread-key.test.ts
#	stores/calendar-store.ts
#	stores/contact-store.ts
#	stores/email-store.ts
2026-10-26 10:14:12 +01:00
VasilisNtovolos 96ae3ce4df fix: reject incomplete email ZIP exports and allow retry 2026-10-21 13:11:41 +03:00
Linus Rath 8428219699 chore: drop the drag test notes from the test folder 2026-10-20 21:15:36 +02:00
VasilisNtovolos cfa16e1bc2 docs: clarify message drag routing verification 2026-10-20 15:49:05 +03:00
VasilisNtovolos 345fe64515 fix: route dragged emails by owning account 2026-10-20 15:45:47 +03:00
Linus Rath 03031fcb13 feat: jump to the next unread message with n #514 2026-10-17 19:18:20 +02:00
Linus Rath 3fd93df4b2 feat: extend the message selection with shift+arrow keys #992 2026-10-17 19:18:15 +02:00
Linus Rath 41a0716c50 Merge branch 'main' into feat/stalwart-1.0 2026-10-16 01:41:48 +02:00
Linus Rath 1cc949416c perf: load the mail view's code while the login form is shown 2026-10-15 03:24:05 +02:00
Linus Rath 97e13a199c perf: stop re-rendering every message row when the list changes 2026-10-15 02:24:58 +02:00
Linus Rath 47762bedf6 perf: re-render only the message rows that changed 2026-10-15 00:41:07 +02:00
Linus Rath 8204678847 fix: stop reporting mail actions as done when a plugin stopped them 2026-10-14 19:44:54 +02:00
Linus Rath be26f96bea Merge branch 'hooks/calendar' into feat/plugin-system-audit 2026-10-14 03:44:13 +02:00
Linus Rath 348ec1d399 feat: let plugins stop app shortcuts and hear when one ran 2026-10-14 03:44:09 +02:00
Linus Rath 89452871fb feat: tell plugins when an event reminder fires and when it is dismissed 2026-10-14 03:40:19 +02:00
Linus Rath 051e82b487 feat: tell plugins about layout changes, drag and drop, and sidebar apps 2026-10-14 03:40:04 +02:00
Linus Rath 9cae33e3cb feat: keep the mountain view compose window open while reading other mail #1229 2026-10-13 15:25:56 +02:00
Linus Rath c633185cda Merge branch 'main' into feat/stalwart-1.0
# Conflicts:
#	CONTRIBUTING.md
#	LICENSE
#	README.md
#	app/api/calendar-agenda/route.ts
#	app/api/wopi/files/[fileId]/contents/route.ts
#	app/api/wopi/launch/route.ts
#	components/calendar/calendar-app.tsx
#	components/calendar/calendar-day-view.tsx
#	components/calendar/calendar-month-view.tsx
#	components/calendar/calendar-week-view.tsx
#	components/calendar/event-modal.tsx
#	components/contacts/contact-detail.tsx
#	components/email/email-composer.tsx
#	components/email/email-list.tsx
#	components/files/file-browser.tsx
#	components/files/files-app.tsx
#	components/files/folder-tree-sidebar.tsx
#	components/filters/filter-rule-modal.tsx
#	components/mail/mail-app.tsx
#	hooks/use-mailbox-drop.ts
#	integration/run-tests.sh
#	lib/__tests__/calendar-agenda-route.test.ts
#	lib/__tests__/jmap-draft-lifecycle.test.ts
#	lib/__tests__/web-push.test.ts
#	lib/account-state-manager.ts
#	lib/jmap/client-interface.ts
#	lib/jmap/client.ts
#	lib/jmap/types.ts
#	lib/message-list-order.ts
#	lib/sieve/generator.ts
#	lib/sieve/parser.ts
#	lib/telemetry/payload.ts
#	lib/thread-utils.ts
#	lib/unified-mailbox.ts
#	lib/web-push.ts
#	lib/wopi/token.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/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/tr/common.json
#	locales/uk/common.json
#	locales/zh-TW/common.json
#	locales/zh/common.json
#	scripts/lite/lib.mjs
#	stores/__tests__/calendar-store-synthetic-ids.test.ts
#	stores/__tests__/email-store-shared-folder-keywords.test.ts
#	stores/__tests__/email-store-unified-actions.test.ts
#	stores/__tests__/file-store.test.ts
#	stores/email-store.ts
#	stores/file-store.ts
#	stores/message-list-tabs-store.ts
2026-10-12 08:35:08 +02:00
Linus Rath 4af42858f0 Merge pull request #1168 from lucamzanon/feat/mountain-view-layout
feat: add Mountain View, an optional alternative layout
2026-10-10 19:23:59 +02:00
lucamzanon 4347d25890 fix(mountain-view): show the admin console link in the top bar
The top bar replaces the navigation rail on desktop, and with it went
the shield that takes a Stalwart admin to the console, so an admin under
Mountain View had no way in. The bar now shows it, with the same update
dot. The check that decides it moves out of the rail into
useStalwartAdmin so both use the same one.
2026-10-09 08:28:40 +02:00
dealerweb 8abb817111 fix: keep a subject with runs of whitespace from freezing the tab title
The browser reads document.title back with its whitespace stripped and
collapsed, so a title with two spaces in a row, a tab or a space at the
edge never reads back as written. useDocumentTitle put its title back on
every <head> mutation whenever the two differed, and its own write was
such a mutation: an endless loop that froze the tab at full CPU, for
example on opening a message whose subject has two spaces in a row. It
now also accepts what the browser read back after its own write.
2026-10-08 18:10:47 +02:00
Linus Rath 2e262b9d5c fix: title the browser tab after the Pro tab in view #1186 2026-10-07 19:34:19 +02:00
Oğuz YılmazandClaude Opus 5.5 8ca7dbbe9b feat: name the active account in the browser-tab title
The tab title was "<context> - <app>", so two mailboxes open side by side
read the same in the tab strip and in the browser's session-restore list.

It is now "<context> - <account> - <app>", the order Gmail and Outlook Web
use: browsers cut a long title from the end, so the part that changes comes
first and the app name last. There is no setting for it; the account is what
tells the tabs apart.

The formatting, and the mail view's choice of context (composer, then
message, then mailbox), live in lib/tab-title.ts with tests. The mail view
sets its title through useDocumentTitle(), which other surfaces can use too.
Next renders the root layout's metadata <title> again on every client-side
navigation, after the surface has mounted; the hook puts its title back when
that happens. Only the surface that set the title last does so, so two
titles never fight.

Refs #1159

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:26:23 +03:00
Linus Rath 6e5c5390df Merge pull request #1162 from dealerweb/fix/focus-trap-rerender
fix: keep the focus in dialogs while the page behind renders again
2026-10-06 23:28:51 +02:00
Linus Rath 0b4d0b0f3c feat: limit the day and week views to working hours and days #1164 2026-10-02 22:56:16 +02:00
dealerweb 0c376dd0e4 fix: keep the focus in a dialog's field while the page behind renders again
useFocusTrap started over whenever its onEscape changed, and callers pass
a new function on every render. Each start moved the focus back to the
opener and then to the dialog's first element, so a field lost the focus
whenever the page behind rendered again: every 30 seconds through the
connection check, or when new mail updated the folder counts. The trap
now reads the latest onEscape from a ref and starts once per opening.

A view change in a dialog (a list giving way to a form) can remove the
element that had the focus. The restart used to pull it back by
accident; the trap now does that on purpose, and only when the focused
element was removed.
2026-10-02 12:42:40 +02:00
Linus Rath 770fb2c84a fix: resolve trash and archive in a background login's own folders #1132
The folder-list cache from #1132 published a reused list only when it
was freshly fetched. When the Pro folder tree had loaded a login first,
the unified build reused that list and never stored it under the
login's JMAP id, so trash and archive for that login's mail were
resolved against the active login's folders until it pushed a change.
A reused list now fills any key nothing has published yet, without
overwriting a live one.

The Pro folder tree also marked a login as loaded before its first
fetch finished and never retried a failure, which left that login's
tree empty until an account switch. A failed load is now retried on
the next run, as the identities hook already does.
2026-09-30 20:35:13 +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 cd14807133 Merge pull request #1112 from theLucius7/fix/config-load-recovery-20260927
fix: recover from transient application configuration failures
2026-09-30 20:32:44 +02:00
lucamzanon 612dcc81ee perf: stop reloading every login's folders and identities on each connection
Two effects keyed on the account list reloaded every connected login each
time the list changed - and while a browser restores its logins it changes
once per connection, so the cost was 1 + 2 + … + N:

- useProMultiAccountMailboxes, which the Pro shell runs for its per-account
  folder tree, reloaded every login's folders: 55 Mailbox/get with ten logins.
  It now loads each login once per client, through the same cache as the
  unified scope, and reloads all of them once on an account switch.
- useProMultiAccountIdentities reread every other login's identities: 45
  Identity/get. Each mount now reads each account once; what is already
  known shows at once, and a read that outlives the run that started it
  still reaches the dropdown.

Both are keyed on the set of connected logins instead of `accounts`, which
also changes for display-name and login-time updates.
2026-09-30 19:04:49 +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
Lucius7nya d923b92034 fix: recover from transient application configuration failures
Retry interrupted or invalid runtime configuration requests with bounded backoff and per-attempt timeouts. Validate the complete response before caching it, and prevent stale HTTP-cached configuration.

Cover request and body timeouts, shared hook consumers, retry bounds, the server response contract, and both Lite configuration paths.
2026-09-27 15:41:21 +08:00
Linus Rath daf67850d2 fix: reply from the receiving account's identity in the unified inbox #1104 2026-09-26 12:31:29 +02:00
Linus Rath 1f35ce8b89 fix: archive, move and mark spam in each message's own account again
Follow-up to 0e0d99e7, which sends the batch actions of a unified view,
a tag view or an "All folders" search to each message's own login and
account.

- Batch Archive in a tag view or the default "All folders" search
  failed with "archive mailbox not found", and "Not spam" in a tag view
  entered from Junk with "Inbox not found": the folder lists came from
  a cache only the unified views fill. In the Pro shell the list cached
  under the bare account id could be another server's, so a browsed
  login's messages were archived or deleted into whatever folder has
  the other server's Archive or Deleted Items id. A batch's Archive,
  Inbox and Deleted Items now come from the list cached for its login
  and account, else from that login's own folder list, never from the
  bare account id.
- "Move to" in a list that mixes accounts looked the picked folder up
  in the login browsed before. In the Pro shell, after browsing a
  folder of another login, the active login's mail went to that other
  server. The menus list the active login's folders, so the folder is
  looked up there now.
- Dragging one row moved every listed row with its id, including
  another account's unrelated message. The drop hands the store the
  dragged rows now.
- "Spam" and "Not spam" on a selected id that is not a row of the list,
  such as a member of an expanded thread, wrote to the account the list
  is reached through, where the id can name another message. Such ids
  are looked up among the expanded threads now, and one found nowhere
  is reported and left alone.
- A move sent in batches (500 messages each by default) stopped at a
  refused batch while the list counted the unsent ones as moved. The
  other batches go out, and only what the server left alone stays
  listed.
2026-09-26 04:33:48 +02:00
Linus Rath 9f7c12ab35 fix: tell attendees when someone with write access changes the owner's meeting
Someone who writes to a calendar shared with them (read & write) changes
the owner's meeting there on the owner's behalf: Stalwart sends the change
from the addresses of the account the event is in. Since 6d4b6954 Bulwark
decided who organizes by the user's own addresses only, so it took the
sharee for an attendee (live on 1.0 and 0.16.23):

- a drag, a resize or a month-view drop wrote the new time without
  scheduling messages: the attendees kept the old time and their answers
  while the owner's copy moved; before 6d4b6954 they got "Updated
  invitation"
- the editor wrote no participants, hid "Send invitations", showed the
  attendees read-only and sent nothing
- "All events" asked nobody again, and "This and following events" ended
  the series quietly and made the rest a new series without attendees

`userOrganizes` now also counts the organizer's own copy in another
account: `isOrigin`, which both versions compute from the addresses of
the account the event is in, true for the owner's meeting and every
expanded occurrence of it, false for an invitation the owner got. Such an
invitation is still changed without messages. The editor no longer drops
the user's own address from the attendees it opens with, only the
organizer's, so a sharee who attends the owner's meeting stays on it.
The demo's invitation from Alice now says it is not the demo user's copy.
2026-09-26 02:44:32 +02:00
Linus Rath 0e0d99e7c3 fix: send unified-view archive, move and spam to each message's account
In the unified views, a tag view and an "All folders" search a
selection can span logins and accounts, and message, account and folder
ids repeat across both: Stalwart numbers the accounts and the system
folders of every install alike.

- Archive sent the whole selection through one login into the Archive of
  the selected folder's account, which is a virtual folder there, so the
  own one: messages of a group account or of another login were archived
  by their ids in the own account, where the same ids can name unrelated
  messages, or failed after the own ones had moved. The list dropped
  every selected row either way. Each login and account now archives
  into its own Archive.
- "Move to" and dragging messages applied one folder id to every
  account, so another account's messages landed in whatever folder has
  that id there, and failed moves were dropped from the list anyway.
  Only messages in the folder's own login and account change folders in
  place now; the others take the cross-account move (Email/copy within
  one login, download and import across logins), a single message moved
  with "Move to" or dragged as well.
- A drop goes to the login whose sidebar list shows the folder. It went
  to the first login whose own account id matched the folder's, which
  with two servers that both number the user `e` could be the other one.
- "Spam" did nothing while the app said the messages were marked, and
  "Not spam" failed, because the selected folder is a virtual one there.
  Both now write each message in its own account and move it to that
  account's Junk or Inbox. The spam shortcut in "All Junk" means "Not
  spam", as the toolbar does.
- What could not be archived, moved or marked stays listed and selected,
  and the error says why.
2026-09-26 02:23:28 +02:00
Linus Rath 6d4b695489 fix: ask attendees again when their organizer drags a meeting
A move outside the editor kept every answer. A drag or resize in the
week and day views, a drop in the month view and an applied counter
proposal wrote the new time with the attendees' stored answers:
Stalwart's update then said "accepted" for a time nobody had accepted,
and a dragged occurrence, which gets its own override, reached no
attendee who had answered (live on 1.0 and 0.16.23). When the user
organizes the meeting these moves now ask the attendees again, as the
editor does; one helper (`userOrganizes`) decides for both.

Also:
- A changed recurrence rule counts as a move for the series: a custom
  rule moved from Monday to Tuesday asks again. A rule the user did not
  touch is no change however the server spells it, and on one
  occurrence, where no rule applies, it is none.
- An event that lasts no time (a deadline) keeps its zero length on
  save instead of becoming an hour long and asking everyone again.
- An attendee's save of their own copy - an alert on one occurrence, a
  drag - wrote the participants again and sent the organizer a
  "Reply:" mail. It now leaves the participants alone and sends
  nothing. The editor shows someone else's attendee list read-only and
  no "Send invitations" box, which only ever controlled the organizer's
  messages; answers go out through the answer buttons.
- An existing override written on the base event (0.16 before 0.16.20,
  and ranges the browser expands) gets the series' organizer again when
  it has none, as every save wrote it before 4c20b09c: 0.16.19 refuses
  an answer to such an occurrence. 0.16.23 takes the key and drops it;
  1.0 refuses it inside an override once, and the store writes the
  override again without it (both live).
- The comment on `organizerCalendarAddress` said Stalwart takes the
  ORGANIZER solely from it. Both versions take it from the owner
  participant when it is missing (live).
2026-09-26 00:25:27 +02:00