Commit Graph
1543 Commits
Author SHA1 Message Date
Shuki Vaknin 9eaaa614fe fix(mobile): keep swipe action icon on the physically exposed edge in RTL
The reveal layer used logical justify-start/-end, which flip under RTL, so
the archive/delete icon appeared on the wrong side. The exposed edge
follows the physical translate direction, not text direction, so force the
decorative layer to LTR.
2026-08-19 03:46:10 +03:00
Shuki Vaknin 78c84f19a5 feat(mobile): swipe list rows left/right for quick actions
Adds Gmail-style horizontal swipe actions to message rows on touch
devices. Swiping a row past a threshold fires a configurable action and
snaps back; a coloured background with an icon is revealed under the
finger and grows when the release would commit.

- New `useSwipeActions` hook: claims a touch as a swipe only once the
  horizontal travel dominates (vertical stays native list scrolling via
  `touch-action: pan-y`, no preventDefault), resists past the commit
  distance, and suppresses the tap that would otherwise follow.
- Two settings (Settings -> reading/email behavior): "Swipe right
  action" (default Archive) and "Swipe left action" (default Delete),
  each one of none / archive / delete / toggle read / toggle star /
  spam. A direction set to an action the current folder does not expose
  simply does nothing.
- Composes with the existing long-press (context menu) handlers; desktop
  is unchanged (hover actions + right-click).
- Scoped to single-message rows; unit tests cover commit/short-drag/
  no-action-side/vertical-scroll/tap-suppression.
2026-08-19 02:18:01 +03:00
Linus Rath 58ddd01cf7 Merge pull request #792 from dev-hive-kazniisa/fix/shared-mailbox-folder-management
fix(email-store): route shared-folder management to the owner account
2026-08-17 12:18:24 +02:00
KazNIISA IT ad0cc108b4 fix(email-store): scope the owner-client lookup to one server
Review follow-up on the shared-folder mutation routing.

The owner lookup replaced the resolved client with any connected client whose
JMAP account id matched. Account ids are opaque per-server values, and personal
mailboxes carry an accountId too, so this ran on every folder mutation and could
hand a mutation to a client of a different server that happens to issue the same
id - where the bare mailbox id may exist and belong to someone else. Keep the
resolved client when it already is the owner, and require a candidate to match
the server URL as well as the account id.

Recover the owner account and the bare mailbox id from the namespaced store id
when no mailbox object is available. Previously the raw `accountId:id` went to
the server as a mailbox id with the owner dropped, so the mutation targeted the
active personal account; in setMailboxRole that cleared the personal role before
failing.

Drop the shared account header context menu when the account carries no usable
id. buildMailboxTree groups shared mailboxes without an accountId under the
placeholder "unknown", which cannot be a mutation target.
2026-08-17 09:10:11 +05:00
Linus Rath c78b964c69 perf: halve time-to-mail-list (lazy locale catalogs, code-split viewer/composer, shorter auth waterfall, boot snapshot) 2026-08-17 01:50:10 +02:00
Linus Rath 344542337a fix: don't export OG_WIDTH/OG_HEIGHT from the og-image route 2026-08-17 00:18:59 +02:00
Linus Rath 117bf4a3f2 fix: import vCard dates as RFC 9553 PartialDate and map common X- extensions #224 2026-08-17 00:09:30 +02:00
Linus Rath d9f9ed10cd fix: add embedded custom app origins to CSP frame-src #787 2026-08-16 23:48:58 +02:00
Linus Rath 22aa328405 feat: branded OpenGraph/Twitter link previews with generated card image 2026-08-16 23:40:25 +02:00
Linus Rath b37313af95 fix: recreate the push subscription on re-register and add per-device revoke #841 2026-08-16 23:35:34 +02:00
Linus Rath eac56ca3bf fix: enforce external media preference on plugin-rendered bodies #797 2026-08-16 23:23:27 +02:00
Linus Rath 452f46f904 fix: hover action background is transparent on tagged rows 2026-08-16 23:16:29 +02:00
Linus Rath 548200131b feat: pick push relay from an admin-defined list instead of a URL field 2026-08-16 23:13:36 +02:00
Linus Rath 526905aa22 feat: implement AddressBook/set in dev mock JMAP server 2026-08-16 23:08:59 +02:00
Linus Rath 5b8ddc352b feat: pick push relay from admin-defined list instead of a URL field 2026-08-16 23:04:22 +02:00
Linus Rath 5a42b3252b fix: iOS Safari zooms the viewport on every input focus #838 2026-08-16 23:02:27 +02:00
Linus Rath 66be55f6a7 feat: show a pull-to-refresh indicator when dragging down #826 2026-08-16 22:50:51 +02:00
Linus Rath d9bdfc3729 chore: remove unused advanced-search-panel component 2026-08-16 22:38:50 +02:00
Linus Rath d5765be1a9 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-16 22:33:02 +02:00
Linus Rath 644a9a0793 fix: search folder filter defaults to all folders and persists #788 2026-08-16 22:32:14 +02:00
Linus Rath 48727489b1 Merge pull request #793 from brambuijs/fix/unclosed-angle-recipient-split
fix: split recipient lists whose angle brackets never close
2026-08-16 22:26:32 +02:00
Linus Rath abc800eab5 fix: do not drop recipients while recovering unclosed angle runs
Review follow-ups on the unclosed-angle split:

- The paste path fell back to splitMailbox for every entry, which keeps the
  last angle run only and folds everything ahead of it into a display name.
  `Ann <a@x.com Bob <b@y.com` became one chip and a@x.com disappeared without
  even landing in `invalid`. Take that path only when the prefix is not an
  address itself.
- The new separator branch ignored `inGroup`, so an unclosed `<` inside RFC
  5322 group syntax shattered the group its sibling branch keeps intact.
- angleRunCloses now scans quote-aware, so a `>` inside a quoted display name
  no longer counts as closing the run, and it starts past `from` so the helper
  is correct whether it gets the separator or the opening `<`.
2026-08-16 22:25:15 +02:00
Linus Rath 83026fe621 fix: keep already written text when applying a template in compose #540 2026-08-16 22:20:49 +02:00
Linus Rath e44f35a203 fix: reuse cached access token on session restore #552 2026-08-16 22:20:07 +02:00
Linus Rath b76ce0d119 fix: folders containing a system folder name disappear from the sidebar #771 2026-08-16 22:11:26 +02:00
Linus Rath 8df57f9ef2 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-16 22:07:19 +02:00
Linus Rath 76f9b96e1d feat: show the server dropdown on the OAuth-only login screen #799 2026-08-16 22:06:22 +02:00
Linus Rath fc0ab57247 Merge pull request #835 from hildebrandttk/fix/hide-tasks-only-calendars
fix(calendar): hide tasks-only calendars from the event calendar UI
2026-08-16 21:59:36 +02:00
Linus Rath 265aeca57c Merge pull request #836 from hildebrandttk/fix/config-route-test-env-isolation
test(config-route): isolate env with vi.stubEnv, fix secret-file race
2026-08-16 21:59:26 +02:00
Linus Rath 034b28598f feat: render plain-text emails in the app font by default, with a monospace option #830 2026-08-16 21:55:31 +02:00
Linus Rath d8c9aaa66a feat: add setting to render plain-text emails in normal or monospace font #830 2026-08-16 21:54:52 +02:00
Stefan Hildebrandt 5fc32c4e89 test(config-route): isolate env with vi.stubEnv, fix secret-file race
The suite intermittently failed "settingsSync ... SESSION_SECRET_FILE" under
full parallel load (passed in isolation). Two causes:

- env vars were mutated directly on the shared process.env, so other test
  files in the same worker could leak in. Use vi.stubEnv + unstubAllEnvs so
  each test's env is isolated and auto-restored, and stub the managed vars to
  undefined in beforeEach for a clean baseline.
- both SESSION_SECRET_FILE tests wrote a shared relative ./session-secret and
  removed it with a fire-and-forget async unlink; under load the pending
  unlink from the first test fired during the second (after write, before
  read), so the file was missing. Use a unique temp file per test, removed
  synchronously in afterEach.

Verified: 17/17, stable across repeated runs and with hostile env
(APP_NAME/SETTINGS_SYNC_ENABLED/SESSION_SECRET) set in the real environment.
2026-08-16 21:32:23 +02:00
Linus Rath ffff165a4b Merge pull request #800 from rotterp/fix/shared-folder-account-context
fix: a directly-selected shared folder has no account context (tags, compose identity)
2026-08-16 21:27:18 +02:00
Linus Rath 60abbc332e Merge pull request #832 from hildebrandttk/fix/sse-single-stream-guard
fix(jmap): keep exactly one SSE stream per client and coalesce state …
2026-08-16 21:27:01 +02:00
Linus Rath eff3869612 Merge pull request #834 from hildebrandttk/fix/recurring-single-instance-override
fix(calendar): edit a single recurring occurrence via a one-shot over…
2026-08-16 21:26:46 +02:00
Linus Rath 3095b3ac4d Merge pull request #833 from hildebrandttk/fix/reply-honor-external-reply-to
fix(reply): honour an external Reply-To even on a self-sent message
2026-08-16 21:26:27 +02:00
Stefan Hildebrandt b5e71e7b84 fix(calendar): edit a single recurring occurrence via a one-shot override patch
The "This event only" edit path built nested JMAP patch pointers
(recurrenceOverrides/<recurrenceId>/<field>). On the first edit of an
occurrence the override object doesn't exist yet, and JMAP pointers can't
create a missing intermediate object, so Stalwart returned notUpdated and
the edit failed with two error toasts.

Build the override as a single pointer recurrenceOverrides/<recurrenceId>
whose value is the occurrence's field set (dropping identity/series keys),
mirroring the working "delete this occurrence" path. Extracted as the pure,
tested helper buildRecurrenceOverridePatch.

Closes #774.
2026-08-16 21:24:52 +02:00
Stefan Hildebrandt 9c084d9d63 fix(calendar): hide tasks-only calendars from the event calendar UI
A calendar holding only tasks (VTODO, e.g. from tasks.org) appeared in the
event calendar UI as an empty calendar. Stalwart's Calendar/get exposes no
supported-component set, so tasks-only-ness is derived from the calendar's
objects (mirroring the existing event/task split): a calendar is tasks-only
iff it holds >=1 object and every object is a task.

- getTasksOnlyCalendarIds scans an account's objects (CalendarEvent/query +
  minimal /get), early-exits once every calendar has an event, only marks
  after a full scan, and fails open on the cap or any error.
- getAllCalendars/getCalendars annotate calendars with isTasksOnly.
- Such calendars stay in the store for the tasks view (task picker, task-list
  grouping) but are filtered from the event sidebar and default selection.

Verified against Stalwart v0.16.16: the tasks-only calendar is flagged, the
default (with an event) is not.

Closes #761.
2026-08-16 21:18:57 +02:00
Stefan Hildebrandt ffdc51ef70 fix(reply): honour an external Reply-To even on a self-sent message
The self-sent handling from #703 (reply to your own thread message ->
address the original recipients, ignore Reply-To) was entered whenever
From matched one of the user's identities. Contact-form / helpdesk mail
commonly arrives From the user's own address with the real correspondent
in Reply-To, so that branch ignored the Reply-To and looped the reply
back to the user's own To.

Only take the self-sent branch when there is no EXTERNAL Reply-To: a
Reply-To resolving to a non-own address is an explicit RFC 5322 directive
and now wins even for self-sent messages (#776). A Reply-To that is only
our own address stays ignored for self-sent (#703 preserved).

Closes #776.
2026-08-16 21:12:21 +02:00
Stefan Hildebrandt 26e8c5797f fix(jmap): keep exactly one SSE stream per client and coalesce state polls
connectSSE() overwrote sseAbortController with a new controller without
aborting the previous one, so re-running setupPushNotifications (which
happens per connected account and on fairly frequent effect changes)
orphaned SSE streams. readSSEStream only unwinds on abort/done, so each
orphan held its HTTP/1.1 socket forever; with multiple accounts this
starved normal JMAP POSTs, which queued and timed out (~30s).

- connectSSE: abort any prior stream (+ its ping monitor / pending
  reconnect) before opening a new one -> exactly one live SSE per client.
- startPollingFallback: bail if a polling interval already exists.
- checkForStateChanges: skip while a poll is already in flight
  (stateCheckInFlight) so overlapping triggers don't stack POSTs.

Closes #781.
2026-08-16 21:09:13 +02:00
Linus Rath 5ab5749306 Merge pull request #802 from dealerweb/fix/keep-alive-test-mock
test: serve a fresh mock response per fetch in the keep-alive test
2026-08-16 13:28:21 +02:00
Linus Rath 527872cc13 Merge pull request #773 from dealerweb/fix/mailto-unsaved-draft-guard
fix: route mailto composer replacement through the close guard
2026-08-16 03:08:44 +02:00
Linus Rath 1e2a4ffb00 Merge pull request #801 from dealerweb/fix/i18n-locale-wiring
fix(i18n): complete the Catalan and Mongolian locale wiring
2026-08-16 03:06:17 +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 3a6d14871f fix: reach the tag and move sub menus in the mobile more actions menu #779 2026-08-16 02:41:56 +02:00
Linus Rath 8e3b4e71a1 fix: open right conversations in shared mailbox #814 2026-08-16 02:19:04 +02:00
Linus Rath e6597542fd fix: include email templates in cross-device settings sync #825 2026-08-16 02:09:38 +02:00
Linus Rath 89139b4055 fix: keep the signature in saved drafts 2026-08-16 01:50:39 +02:00
Linus Rath 413dd54bcf Merge branch 'main' of https://github.com/bulwarkmail/webmail
# Conflicts:
#	stores/auth-store.ts
#	stores/settings-store.ts
2026-08-16 01:27:40 +02:00
Linus Rath 1213c90f23 Merge pull request #824 from paulhenry46/rangeHeader
feat: allow privileged plugin to fetch only a range of blob
2026-08-15 19:45:56 +02:00