Commit Graph
16 Commits
Author SHA1 Message Date
Linus Rath 4a9284aa48 fix: mark the answered or forwarded message from a Pro compose tab also when the send is held for undo 2026-10-26 16:14:18 +01:00
Linus Rath 54b197080c fix: keep a discarded draft of a signed-out account instead of deleting another account's message with its id 2026-10-26 16:11:52 +01:00
Linus Rath 0b917d6262 fix: mark the replied, forwarded or receipted message as answered in its own account, not another account's message with the same id 2026-10-26 16:11:52 +01:00
Linus Rath 8c9cf8e63e test: check draft account and threading headers in createDraft's options after the merge 2026-10-26 12:26:57 +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
Peterka35 bbc3c2123e fix: follow markAsReadDelay when expanding a conversation or opening a Pro folder row
Expanding a conversation in the message list (row click, expand chevron,
or the `x` shortcut) and clicking a row in a Pro folder tab marked the
messages read straight away. They ignored "Mark as read: Never" and any
delay, which the reading pane and the conversation view follow.

Route them through one helper, scheduleAutoMarkAsRead: -1 never marks,
0 marks at once, any other value marks after that many milliseconds and
returns a cancel function.

- New store action autoMarkExpandedThreadAsRead keeps one pending timer
  per thread key. Collapsing the thread, or collapsing all threads, drops
  it. Both expansion paths (EmailList and the `x` handler in MailApp) use
  it.
- MailApp's handleEmailSelect drops the pending mark of every expanded
  thread other than the one the newly opened message belongs to, through
  cancelAutoMarkThreadAsRead. Moving on with `j`/`k` or a click no longer
  lets the delayed mark land on a conversation the user has left. The
  thread stays expanded.
- ProFolderTabBody schedules the row mark through the helper and drops a
  pending one when another row is opened or the tab unmounts.
2026-10-17 21:01:34 +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 5f0669eb92 fix: stop reopened drafts from deleting another login's mail
A reopened draft is saved over and removed by id through the login and
account it is stamped with, and message ids repeat across servers. Two
ways of opening a draft stamped it with a login that was only on
screen, not the one it was read through:

- A Pro email tab opened by double-click named no login. It read the
  message through whatever login the main list was browsing and stamped
  the active one: with the 0.16.23 login active and the 1.0 login's
  Drafts browsed, the first autosave destroyed the draft's id in the
  0.16.23 account and left the draft itself behind. A Pro view now
  stamps only a login its host names, and reads the message through
  that login's client. A double-click on a row of the mail list or of a
  folder tab names the row's login and account, and the search preview
  and a folder tab's reading pane name theirs for the active login
  too. Without a login the composer goes by the draft's From, as it did
  before.
- "Cancel and edit" in Scheduled took the login browsed before, which
  stays set while Scheduled lists the active login's mail. The draft
  was restored through the active login and then saved over and
  discarded through the other one. It now takes the active login, and
  a send undone or cancelled for editing never falls back to what is on
  screen.

Also:

- A draft version saved through another login is removed only once the
  message is out. A host without a connection sent nothing and the
  version was removed anyway; it now says so and the draft stays.
- An email tab opened for one login's message no longer focuses the tab
  of another login's message with the same id and account.
2026-09-26 04:32:25 +02:00
Linus Rath aa99f038d1 fix: keep a scheduled message when its new time loses a recipient
"Send now" and "Reschedule" create a replacement submission and then
cancel the original. The server checks the recipients again for the
replacement, and a throttle used up since the original was taken ("452
4.4.5 Rate limit exceeded") can refuse some of them. The original was
kept only when every recipient was refused; on a partial refusal it was
cancelled, and a recipient the original had taken lost the message
without anyone being told.

- The original's delivery status is read with its envelope. When the
  replacement refuses a recipient the original took, the replacement is
  cancelled (its record only destroyed when it took nobody) and the
  original goes at its old time. A recipient the original refused as
  well loses nothing, so the reschedule goes ahead; checked live on
  0.16.23 and 1.0 with a relay-refused recipient.
- The toast for a kept schedule leads with "The message is still
  scheduled for ..." and says "Nothing was changed: the server refused
  ...". It used to open with "Not sent", which invited sending the
  message a second time.
- Recipients refused while the others were taken are now named after
  "Send now", a reschedule from the list or the viewer, and a quick
  reply, which said "Email sent".

Files, from the review of d9ffe8a7:

- An own folder opened from search while Files showed a shared folder
  opened the shared folder again: its link had no query, and the page
  read `?shared=` from the address bar. The link now says it has none.
- The Pro shell kept only the path of a Files link across its redirect,
  so a shared folder's link (`/files?shared=...`) didn't survive a
  reload, and neither did `?preview=`. The query goes along now.
- A search hit for a file on a "Files from" page opened the first file
  of that name there; it is found by the id the link names now.
- Undoing a paste of files cut on a "Files from" page moved them to
  "*", the page's id; they go back to their own folders now.
2026-09-26 02:25:40 +02:00
Linus Rath ab72086e00 fix: remove a draft's old versions only through the login that saved it
A draft's old version is removed by id when the composer saves over it,
sends it or discards it. Email ids repeat across accounts and servers -
the same user logged in to two servers is account `e` on both - so the
removal must go to the login and account the draft lives in. Two paths
took the wrong one:

- "Edit draft" in an "All folders" search or a tag view took the account
  from the folder still selected underneath. With a group folder
  selected, an own draft was opened as the group's, and its first
  autosave, send or discard destroyed the message with that id in the
  group account, while the draft stayed in the own Drafts. It now takes
  the login and account the result is stamped with, reads the draft
  through that login, and prefers that login's identity when two
  logins share an address.
- The composer discarded through the login of the current From, and
  replaced or sent through it too. After switching From to another
  login's identity - which saves nothing by itself - discarding,
  the next save or the send removed the draft's id on the other server.
  Each save now records the login it went through (a draft opened from a
  list starts with the list's). A version saved through another login is
  removed through that one: after the new version exists when saving,
  after the message is out when sending, and on discard.

The Pro email view, folder tabs and search preview pass their login
along with the draft.
2026-09-26 02:24:35 +02:00
Linus Rath e0b0dfe0cc fix: stop reporting mail as sent when the server refused its recipients
Stalwart runs the SMTP RCPT stage while it creates a submission and
records a refused recipient as `delivered: "no"` with its reply: a relay
it doesn't allow ("550 5.1.2 Relay not allowed.") or a used-up throttle
("452 4.4.5 Rate limit exceeded, try again later."), on 0.16 and 1.0
alike. The create still succeeds and onSuccessUpdateEmail files the
message in Sent. Bulwark read only the new record's id and sendAt, said
"Email sent", and on Stalwart destroyed the record right after, the only
trace of the refusal. The message was lost without a bounce.

- the send reads the new submission's delivery status in the same
  request (`EmailSubmission/get` by its creation id; the create's answer
  has none), before the record is destroyed
- every recipient refused: the send fails with the server's reply per
  address, the copy filed in Sent is removed and the user's draft kept,
  so the address can be fixed in the composer; held sends (undo send,
  scheduled) are refused when created, so this covers them too
- some recipients refused: the others get the message, and a warning
  names the refused ones with their replies
- "Send now" and "Reschedule" keep the original when every recipient of
  the replacement is refused, instead of cancelling it
- the submission prune leaves a shared account that refuses its records
  alone for the session, without a warning: 0.16 lists an account that
  only shared an address book or a calendar with the submission
  capability
2026-09-26 00:08:12 +02:00
Linus Rath 8a83cae6d9 fix: restore undone sends from group identities in the group account
Mail sent from a group or shared identity (team@example.org) is created
in that account, together with its submission, on Stalwart 0.16 and 1.0
alike. Undo send and "Cancel and edit" cancelled the submission there,
but then put the message back in Drafts, read it back and later removed
it through the user's own account. Email ids repeat across accounts - on
the test servers a new team message and a message in userb's inbox were
both `iaaaaac` - so an unrelated own message was moved to Drafts, opened
in the composer and destroyed on Discard, while the undone message stayed
in the group's Sent.

The account the message was sent from now travels with it:
- `restoreEmailToDraft` takes the account, the store looks up that
  account's Drafts by role, and the restored draft is read from there
- the composer keeps the draft's account (`draftAccountId`); saving,
  sending and discarding remove the old version in that account, and the
  first save moves the draft to the own Drafts, where drafts are saved
- discarding goes through the login the draft was saved with, as the
  send does (#461)
- "Edit draft" on a message in a shared or group folder, in the standard
  and the Pro views, opens it with that folder's account, so a draft left
  in the group's Drafts is not removed by id in the own account either
- S/MIME copies, and the unused `cancelScheduledEmail`, are deleted in
  the submission's account
2026-09-26 00:03:23 +02:00
Patrick RotterandClaude Opus 5 129f545aeb fix(pro): default the compose tab's From to the open mailbox
`ProComposeTabBody` rendered `<EmailComposer>` without
`composeFromAccountEmail`; only the standard shell in
`components/mail/mail-app.tsx` passed it, so `mode === 'compose'` resolved
`findComposeIdentityId(identities, undefined)` to null and every new message
in the Pro shell defaulted to the account owner even with a shared folder
open. That covered the whole compose surface there, since the Pro shell
hoists every "show composer" intent - `mailto:` links included - into a tab.

Resolved exactly as the standard shell does, so both share one rule, but
read once at mount: a tab body stays mounted while the tab lives, so
subscribing to the mailbox list would re-render the composer and its editor
on every counter refresh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 20:36:44 +02:00
Linus Rath 3e5ed4f3c4 feat: folder tabs via drag & drop + sidebar in fullscreen email tabs 2026-08-20 15:06:45 +02:00
Linus Rath 47cf0ed90d feat: rework pro split-screen shell, per-pane tab strips, drag & drop, pane-scoped overlays 2026-08-16 03:03:40 +02:00
Linus Rath 706c6c23fd fix: send via the identity's own account client so DKIM matches the From domain #461 2026-08-06 18:55:08 +02:00