Commit Graph
430 Commits
Author SHA1 Message Date
Linus Rath a025b78ed4 test: pin the fixed Message-ID, the found copy and the conflict list 2026-11-03 06:16:09 +01:00
Linus Rath 687e7cc012 feat: move offline mode onto the encrypted local store
The IndexedDB cache, its crypto, the responder and the page-side sync
go. lib/offline keeps its structure, policy, wipes, sign-out gates and
notices; answers, the outbox and sync now live in lib/store, in a worker
behind lib/store/client/api.ts.

What users get besides that:
- every folder is kept, not just the Inbox and opened ones
- delete for good and empty trash work offline
- a change the server refuses, or that meets a message changed on
  another device, is shown instead of dropped
- a send whose response got lost is looked up by a Message-ID fixed when
  it was queued, then finished, submitted or sent again
- with the store on, a start paints from the local copy, then connects

An account the app drops by itself no longer wipes its data: only a
sign-out or an explicit removal does. The outbox of the old database
moves over once, in lib/offline/migrate-legacy.ts, and the old database
goes only after the store committed. `isNetworkFailure` now counts
timeouts and proxy 5xx. Sends and outbox requests are never answered
from the device.

Tests moved with the code: cache.test.ts and responder.test.ts are
replaced by lib/store/__tests__/, and review-fixes.test.ts, outbox.test.ts
and client-fallback.test.ts now run the real engine on node:sqlite.
Changed assertions: an uncertain send is looked up rather than left alone
forever, destroy and empty trash are queued rather than refused, and #9
(opened folders) became "a folder the server deleted goes", since all
folders are kept.
2026-11-03 05:54:39 +01:00
Linus Rath e9e07ddb64 Merge main into feat/assistant-everywhere 2026-11-03 01:35:57 +01:00
Linus Rath 8034b005b8 fix: keep archived messages in the Archive and in search results after a plugin's batch, and update folder counts 2026-11-03 00:53:35 +01:00
Linus Rath 23c410ddea fix: ask the server whether a folder is shared before a plugin's move, filter or archive files mail there 2026-11-03 00:29:02 +01:00
Linus Rath 6a6a8e829c feat: let batch archive and tags act on the messages a caller names 2026-11-02 23:14:22 +01:00
Linus Rath c7e88c249e fix: sync the setting that deletes junk mail for good between devices 2026-11-02 23:14:22 +01:00
Linus Rath 732175772c fix: carry a plugin's template or signature change into both rich and plain-text drafts 2026-11-02 22:52:04 +01:00
Linus Rath 72117c8a71 Merge branch 'main' into feat/proofreading
# Conflicts:
#	components/admin/admin-shell.tsx
2026-11-02 21:27:17 +01:00
Linus Rath 6055dac1cb fix: keep proofreading text from plugins that shouldn't get it, and work it by keyboard #1397
A security and an accessibility review of the proofreading hook found
gaps, fixed here.

What leaves the browser:
- A checker gets a name for each composer of its own, not the composer's
  id, so it can't reach the message through the other api.composer calls
  after proofreading stops.
- Checking pauses while a plugin that can sign or encrypt is turned on but
  still starting, and while the policy couldn't be fetched (the built-in
  defaults could undo an admin's lock).
- No plugin can hold up turning proofreading off (onBeforeSettingChange).
- Suggestions can't carry zero-width, bidi or line-separator characters;
  messages lose bidi overrides; a flood of annotations is cut off early.
- The notices name the plugin as installed, not a label it chose, and say
  that text written before signing or encryption was turned on may
  already have been checked.
- A checker registered during start-up no longer fails its first check.

Keyboard and screen readers:
- Escape in the issue panel closes the panel, not the message.
- Underlines carry aria-invalid, as the browser's own markers do.
- The focus goes somewhere sensible when a button or an issue goes away,
  Tab past the popup's edges goes back to the text, and the shortcuts work
  in the popup and the panel.
- Ctrl+' and Ctrl+; follow the character typed, so other layouts keep
  their own keys (Ctrl+S on Dvorak), with Ä and Ö on German keyboards.
- The popup is described by its message, sits inside a modal composer,
  fits narrow screens, and the panel fits a phone.
2026-11-02 20:26:21 +01:00
Linus Rath 7b59f2562f Merge pull request #1493 from ibayue/feat/signature-insert-toggles 2026-11-02 20:23:58 +01:00
Linus Rath d70f058ec7 fix: keep the signature delimiter on by default for new users #1493 2026-11-02 20:15:13 +01:00
Linus Rath 52330cbd07 fix: keep inserting signatures for existing users by default #1493 2026-11-02 20:14:48 +01:00
Nick Sweeting c862909fdd fix: respect single-call paging and preserve folders on page failures 2026-11-02 10:50:50 -08:00
Linus Rath 5fc65b47cc feat: let admins require or turn off proofreading per group and plugin #1397 2026-11-02 19:46:01 +01:00
daluchenandClaude Code 1f8b1f4533 feat: add signature auto-insert toggles, off by default
Automatic signature insertion was unconditional: any identity with a
signature got it embedded in new mail and appended to replies, forwards
and quick replies. Users who want a signature only in specific contexts
had no way to opt out.

Add two per-account synced settings, both defaulting to off:

- signatureNewMailEnabled: insert the signature in new messages (and
  re-opened drafts, which open in compose mode)
- signatureReplyForwardEnabled: append it in replies, forwards and the
  quick reply box

With a toggle off the composer neither embeds the signature into the
body, nor appends one at send or draft-save time, nor shows the
read-only preview under the editor - the preview would promise a
signature that never goes out. A signature pasted in by hand is never
stripped (bodyCarriesSignature still short-circuits the append).

Also flip signatureSeparatorEnabled's default from true to false: the
RFC 3676 "-- " delimiter surprised users more than it helped. Only the
default changes - no migration, so users who already set it keep their
choice.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-02 11:05:17 +08:00
Linus Rath 3c2eb9bffa feat: let a new search look only in the open folder #1485 2026-11-01 17:43:51 +01:00
Linus Rath 4b60fcc1e6 fix: make the offline outbox never send twice, never lose mail silently and never skip a plugin #523
The review of offline mode found ways to send a message twice, to lose
unsent mail without a word, and to send mail without its signing,
encryption or check plugin. This fixes them and the smaller findings:

- each outbox message is taken for sending in one database transaction
  under a Web Lock per account; only a failure before the submission
  request (SendNotStartedError from the client) puts it back, any other
  one marks it "may have gone out"; read-backs after the submission no
  longer fail a send; a send of a closed tab turns uncertain after a minute
- every sign-out the user didn't confirm names the deleted messages on
  the sign-in page, or in a toast when another account stays signed in
- nothing is queued while an enabled plugin isn't running (after a start
  without the network none can); the composer says why
- the outbox waits for a fresh policy and holds messages the rules have
  since made stricter (a required label, a required send check)
- another tab switching offline mode off sticks: only switching it on
  may create the database
- only attachments of kept messages are kept, filed with their message,
  deleted with it and under the cap between syncs; file names and types
  are sealed; folders that went away leave the scope, and a folder that
  couldn't be asked keeps its mail without holding the others back
- a group scope that forbids offline mode waits until the account's
  groups are known; one server down no longer holds another's mail
- an offline start only when the server is really unreachable, and the
  real sign-in follows by itself once it answers
- the service worker files pages and chunks under the build that served
  them, and the shell list of another build isn't kept under this one
- a raw send counts as the composer's only from the plugin running its
  takeover, for that identity
- errors and refusals are in all languages; the banner tells "offline"
  from "server can't be reached"; outbox buttons name their message;
  changes and messages go in the order they were made
2026-11-01 14:19:54 +01:00
Linus Rath b99003956a feat: show an admin announcement across the apps 2026-11-01 07:51:46 +01:00
Linus Rath a8f9d81337 feat: block the calendar while the out-of-office reply is on 2026-11-01 05:19:07 +01:00
Linus Rath 877198a4bf feat: answer invitations in a calendar shared with you as its owner #634 2026-11-01 05:05:12 +01:00
Linus Rath bdac27367c fix: keep the organisation's locks when the policy can't be fetched 2026-11-01 04:20:02 +01:00
Linus Rath 4842e08cde fix: show shared mailboxes under each account when several are signed in #1486 2026-11-01 00:24:57 +01:00
Linus Rath eb2e3b2baa feat: make the attachment bar the default and retire attachments beside the sender 2026-10-31 23:35:54 +01:00
Jacob Zeitz fca08981a1 feat: compact shared mailboxes in the sidebar #1342
Closes #1483
2026-10-31 22:17:39 +01:00
Linus Rath 136e6be8fd refactor: drop the unused draft autosave interval setting 2026-10-31 18:07:16 +01:00
Linus Rath f11ddf82dd fix: tell banner plugins when an admin opened the mailbox through impersonation 2026-10-31 17:48:29 +01:00
Jacob Zeitz d5cd3c6e40 feat(plugins): star and after-send hooks name the mail's account
Message ids repeat across accounts, so a hook payload naming only an
emailId could not tell a plugin which mailbox the message lies in when
it is a shared or group account's.

onBeforeEmailStarToggle and onEmailStarToggle now carry accountId when
the starred message is not in the user's own account, as the
message-row-actions click context does. Every star (the row's button,
the reader's star, the key, the menu) goes through toggleStar, one
message at a time, so one accountId names it; the account is resolved
before the plugins are asked. An own message's payload is unchanged.

onAfterEmailSend now carries accountId, the JMAP account the sent
message lies in (a mail sent from a shared or group identity lies in
that account), so api.email.get finds it. sendEmail returns that account
as emailAccountId, the S/MIME send too. The hook also carries
composerId, the id the sending composer's slots and actions gave
plugins, so a plugin can act on what the user chose in that composer.
2026-10-31 14:15:06 +01:00
Linus Rath 2de0ee8e30 fix: keep tag text readable on unread, selected and hovered rows 2026-10-31 02:20:40 +01:00
Linus Rath ced40c7e5c feat: keep list rows plain instead of tinting them by tag by default 2026-10-31 01:32:29 +01:00
Linus Rath 5eabdd8904 feat: play the involved sound for new mail by default 2026-10-31 01:21:02 +01:00
Linus Rath dafdda5347 fix: let search results move into the open folder #1452 2026-10-31 01:16:34 +01:00
Linus Rath 4a0772dc01 fix: open the next unread message after archiving in unread first #1453 2026-10-31 00:37:05 +01:00
Linus Rath a225109400 fix: keep the open folder when clearing search filters #1430 2026-10-29 20:11:08 +01:00
Linus Rath be423637fc feat: show one column and an optional paged message list in simple mode 2026-10-29 19:14:41 +01:00
Linus Rath 6e45d754f9 fix: keep pushed notifications silent while do not disturb is on 2026-10-29 19:10:30 +01:00
Linus Rath 842c369fae fix: save open drafts before an expired session signs out and say when one could not be saved 2026-10-29 18:03:01 +01:00
Linus Rath 8008393e59 feat: add an Accessibility page to Settings
Settings get a page of their own for accessibility, with presets on top
(screen reader, low vision, dyslexia, focus, keyboard and motor, motion
and seizure safe, deaf and hard of hearing) that change several options
at once and can be undone, then the options by topic, and a reset.

Text size used to be a fixed 14/16/18px on the root, which threw away
the size people set in their browser. It is now a percentage of the
browser's size, from 87.5% to 200%. Motion, contrast and transparency
follow the system by default and can be overridden either way; the
old Animations switch becomes Reduce motion. The visual options are
applied before the first paint, so the page no longer flashes at the
wrong size.

- `lib/accessibility-settings.ts`: the new settings, their allowed
  values, and the code that writes them onto `<html>`
- `app/accessibility.css`: bundled fonts (Atkinson Hyperlegible Next,
  Lexend, OpenDyslexic), text spacing, a focus outline that survives
  Windows contrast themes, more-contrast colours on any theme, reduced
  transparency, a large pointer
- admins can preset every new option but lock only shortcuts, send
  confirmation and the notification duration; a lock on text size or
  another reading aid works as a default
- always-light mail and toolbar labels now sync, and imports drop
  values the page doesn't know
- font size and animations move off Appearance, the plain text font
  off Reading; everyday settings such as conversation view and undo
  send show on both their own page and Accessibility
2026-10-29 17:53:32 +01:00
Linus Rath c8bfc46091 fix: update folder counts as soon as a send removes its draft #1412 2026-10-29 14:32:26 +01:00
Linus Rath c7d0525f04 fix: stop waiting for plugin search results when the search fails 2026-10-29 14:14:10 +01:00
Jacob Zeitz 03876158fe feat(plugins): hold the search results block's place while a plugin still searches
A plugin's results to a search came all at once after its handler answered,
with nothing before: the block then appeared above the message list and
pushed it down, under a pointer about to click a row. onProvideSearchResults
handlers that search elsewhere (a CRM's search by meaning) take seconds.

Each plugin's answer now shows as it comes, and the store keeps the plugins
still asked. One that has not answered after 300 ms gets the block at a fixed
height with placeholder rows under "Searching for more results…"; its
entries then fill that height, so the list moves once, with its own results.
A plugin that answers at once (one not searching for this query) shows
nothing.
2026-10-29 10:25:59 +01:00
Linus Rath f12a13e1fd feat: create documents in the formats the document editor can edit from New file #1393 2026-10-29 00:01:13 +01:00
Linus Rath ba13866311 feat: print a clean copy of the page instead of the app shell 2026-10-28 21:08:20 +01:00
Linus Rath baa5577ea7 Merge pull request #1360 from arnauda-gh/fix/scheduled-email-date-and-conversation-status
# Conflicts:
#	components/email/email-viewer.tsx
#	components/email/thread-conversation-view.tsx
#	components/mail/mail-app.tsx
2026-10-28 17:14:23 +01:00
Linus Rath 8c0c314ebd Merge pull request #1400 from lucamzanon/perf/tag-counts-hidden-tabs 2026-10-28 17:02:24 +01:00
Linus Rath 96797597c5 Merge branch 'main' into feat/org-features 2026-10-28 15:14:34 +01:00
Linus Rath 99b3f18122 fix: stop the demo from asking the server for the account principal and logging an error 2026-10-28 12:57:23 +01:00
Linus Rath e654eadd37 Merge branch 'main' into ux/mail3 2026-10-28 11:10:56 +01:00
Linus Rath 780b64461e fix: close a message in the reader once its scheduled send is cancelled instead of still showing it as scheduled 2026-10-28 10:28:11 +01:00
Linus Rath 10420b8d96 fix: show the real path of a folder picked in the Files tree, never the hidden trash 2026-10-28 10:18:14 +01:00