Commit Graph
1588 Commits
Author SHA1 Message Date
Linus Rath a29cf27ff8 fix: route multi-account email lookups and invitation parsing by source account #847 2026-08-25 18:50:50 +02:00
Linus Rath 5109aa059a fix: decode percent-encoded FileNode names from WebDAV-created nodes #869 2026-08-25 18:47:22 +02:00
Linus Rath f189e0bab6 fix: refresh account display name from the Stalwart principal on login/restore/switch #900
Stalwart seeds the default identity name from the principal "Full name" only
once, and addAccount never rewrote displayName after first login, so an admin
rename never showed up in Bulwark. Read the live name via x:Account/get
through the passthrough (falling back to the identity name elsewhere) and
prefer the synced registry name in account settings.
2026-08-25 18:45:44 +02:00
Linus Rath c65da9d76a Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-25 18:40:00 +02:00
Linus Rath 2e650a93b4 Merge pull request #888 from texchi2/fix/insecure-origin-randomuuid
fix: guard crypto.randomUUID for insecure origins (toast store, message-id, contact uid)
2026-08-25 18:39:24 +02:00
Linus Rath ed340cc2c1 feat: make Stalwart admin access to the dashboard configurable #870 2026-08-25 18:39:07 +02:00
Linus Rath 342217ad99 fix: keep read/unread toolbar button a fixed width so buttons don't jump on open #864 2026-08-25 18:16:51 +02:00
Linus Rath 9a19ddbbac Merge pull request #890 from paulhenry46/wkd
feat(plugins): new plugin API method: getPublicKeyFromWKD
2026-08-22 21:43:04 +02:00
Paulhenry Saux d82a8e5ca5 feat(plugins): new plugin API method: getPublicKeyFromWKD 2026-08-22 20:55:45 +02:00
texchi2andClaude Fable 5 5e0efff091 fix: toast store crashes on insecure origins (crypto.randomUUID undefined), breaking every post-action acknowledgement
addToast() minted toast ids with crypto.randomUUID(), which only exists in
secure contexts (https/localhost). On a plain-http LAN origin every
toast.success/toast.error threw a TypeError, so e.g. calendar event Save
crashed AFTER a successful CalendarEvent/set — before the dialog-close ran —
leaving the dialog open reporting failure while the server had the event;
retries created duplicates. Same class as the generateMessageId send bug.

Also guards the same-class contact uid fallback in lib/jmap/client.ts
(createContact). Both now use the guarded generateUUID() from lib/utils.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtFkcUA8VM2XAKZKzptpqy
2026-08-22 19:58:18 +08:00
texchi2 a92c852cc8 fix: generateMessageId crashes on insecure origins (crypto.randomUUID undefined) — use guarded generateUUID(); send failed after draft save, before EmailSubmission 2026-08-22 18:09:59 +08:00
Linus Rath eb9df28270 Merge pull request #886 from shukiv/feat/group-push-notifications
feat(push): group new-mail notifications per account instead of one per message
2026-08-21 23:16:43 +02:00
Linus Rath 5e27803573 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-21 18:05:32 +02:00
Linus Rath 7e9aeaf0d4 fix: calendar click/double-click created events near current time instead of the clicked slot 2026-08-21 18:04:33 +02:00
Shuki Vaknin d29ab9b552 feat(push): group new-mail notifications per account instead of one per message
Each email push showed a notification with a unique tag
("bulwark-mail:<id>"), and Android renders one notification per unique
tag — so a burst of mail stacked one toast per message (50 arrivals =
50 toasts).

Use a shared per-account tag ("bulwark-mail:<accountId>") so each push
replaces the account's single notification. The newest message stays the
headline and a Gmail-style "+N more messages" line, counted from the
account's unread total, carries the rest. A single unread still deep-links
to that message on click; a group opens the inbox.
2026-08-21 18:48:04 +03:00
Linus Rath a5d08b573a Merge pull request #862 from dealerweb/fix/signature-unlock
fix: let a double-click unlock the embedded signature for editing
2026-08-21 09:26:50 +02:00
Linus Rath 6052f32129 Merge pull request #876 from dealerweb/fix/sent-draft-stale-viewer
fix: clear the viewer when sending destroys the displayed draft
2026-08-21 09:26:26 +02:00
Linus Rath 165ec6d321 Merge pull request #851 from paulhenry46/webauthn-fix
fix(plugins) : refactor PRF workflow into two for better compatibility
2026-08-20 23:43:12 +02:00
Linus Rath 93fd9068f7 Merge pull request #863 from shukiv/feat/mobile-swipe-actions
feat(mobile): swipe list rows left/right for quick actions
2026-08-20 21:59:15 +02:00
Linus Rath 6a3bb3f459 feat: redesign the unread favicon badge as a compact keyline badge 2026-08-20 16:49:31 +02:00
Linus Rath 161e7ec92a i18n: restore key parity for pull-to-refresh and folder move_error strings 2026-08-20 16:33:33 +02:00
Linus Rath 37d85e5447 feat: fullscreen email view in the standard interface 2026-08-20 16:19:12 +02:00
Linus Rath fc88fc6d3b feat: drag a mail to a new browser tab to open it fullscreen 2026-08-20 15:55:44 +02:00
Linus Rath 65e70a8088 feat: address bar reflects the focused Pro tab + live deep-link delivery to mounted surfaces 2026-08-20 15:35:41 +02:00
Linus Rath d83c83b0a9 fix: drop the folder sidebar from fullscreen email 2026-08-20 15:11:25 +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 485b93f777 fix: lengthen example SESSION_SECRET 2026-08-20 14:44:19 +02:00
Linus Rath b9fe961ae8 fix: session cookie not stored for relative JMAP server URLs (dev mock logouts) 2026-08-20 14:42:29 +02:00
Linus Rath 2d5748e6c9 fix: leaving the Pro interface returns to the surface in use and keeps the settings scroll position 2026-08-20 14:35:05 +02:00
Linus Rath 1c6cf60bbf fix: preserve settings scroll position when the Pro interface toggle remounts the settings surface 2026-08-20 14:29:16 +02:00
Linus Rath 46256cddba fix: position portalled popovers before first paint to stop layout flash on open 2026-08-20 14:23:05 +02:00
Linus Rath 54d7fb6fc9 fix: folder drag & drop can move folders into other parents #855 2026-08-20 14:21:41 +02:00
Linus Rath 50741d1131 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-08-20 14:00:42 +02:00
Linus Rath 9a6cc23876 fix: refresh TOTP-minted tokens with the default client id when no OAuth client is configured #873 2026-08-20 13:59:57 +02:00
Linus Rath 434005b170 fix: pass PDF bytes to pdf.js instead of fetching the blob: URL, which CSP connect-src blocks #871 2026-08-20 13:57:19 +02:00
Linus Rath a95267ba59 Merge pull request #853 from dev-hive-kazniisa/fix/jmap-keyword-patch-pointer
fix: keyword patches use an unescaped JSON Pointer, and a failed migration reports as a clean one
2026-08-20 13:53:39 +02:00
Linus Rath 3c299baa3e Merge pull request #831 from zisisvakras/fix/email-keyword-removal-uses-null
fix(jmap): remove keywords with null rather than false in Email/set
2026-08-20 13:37:42 +02:00
Linus Rath 5d2b937ae3 fix: keep attachments when re-opening a draft and destroy old draft versions only after a successful create/send #849 2026-08-20 13:25:25 +02:00
dealerweb 0fd83203cf fix: clear the viewer when sending destroys the displayed draft
Sending an edited draft destroys it server-side, but the viewer kept
rendering the deleted message after the composer closed: the mailbox
list refreshed (row gone, counter down) while selectedEmail still
pointed at the destroyed draft. Clear the selection like a delete does
- and return to the list on mobile - when the email on display is the
draft the compose session was opened from or its latest autosaved
successor (autosave replaces the draft under a fresh id on every save,
so matching the sent draftId alone would miss).
2026-08-20 13:12:06 +02:00
dealerweb bc5b18bea9 fix: address the review notes on the signature unlock
- hoist only font longhands from wrapper divs: applying the `font`
  shorthand after the line's own styles would reset the font-family/
  size the line declares itself; the CSSOM exposes shorthand
  declarations through the longhand queries anyway
- only preventDefault on dblclick when the unlock actually ran, so a
  read-only editor keeps its default double-click behaviour
2026-08-19 11:56:18 +02:00
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
dealerweb 3d1bdf0943 fix: let a double-click unlock the embedded signature for editing
Since the signature became a verbatim, non-editable atom (#476), there
was no way to edit it in the composer at all (#822) - the styling
guarantee silently cost the editability #329 had established. Keep the
atom as the default so branded signatures still survive 1:1, and let an
explicit gesture opt out: double-clicking the block dissolves it into
regular editable content at the same position. Double-clicks that land
on a link are ignored (the click is working the link), and Enter on the
selected block keeps ProseMirror's default create-paragraph-after
behaviour.

Faithfulness of the dissolved signature:
- Leaf <div> lines become paragraphs; only vertical margins the line
  does not declare are reset to 0 (how a div actually renders), so
  explicit spacing and indents survive. Divs carrying hr/pre and other
  non-line blocks are left to the schema parse.
- Inherited styles (font, color, ...) on wrapper divs - which have no
  schema node and would be dropped - are hoisted onto the generated
  line paragraphs, the line's own declarations winning.
- The content is parsed as a CLOSED document via the schema DOMParser
  and spliced in with one transaction; insertContentAt fits an open
  slice, which can split text runs at hard breaks and turned <br>-lined
  signatures into one spaced paragraph per line.

The bracketing data-signature-block markers live outside the atom and
stay put, so the identity-switch splice and the send path's
already-embedded detection keep working on unlocked content, and
switching identities re-embeds a fresh locked block. Undo restores the
atom. A translated tooltip on the block makes the gesture discoverable.
2026-08-18 17:31:47 +02:00
KazNIISA IT 5c484af1f7 fix(jmap): escape the JSON Pointer in keyword patches, and migrate a keyword honestly
A PatchObject key is a JSON Pointer (RFC 8620 section 5.3), so a slash
inside the member name it points at has to be escaped as `~1`. Keyword
patches were built as `keywords/${keyword}` verbatim, which is fine for
every flat keyword but wrong for a nested tag: `$label:work/clients`
became the pointer `keywords/$label:work/clients`, asking for the
`clients` member of the `$label:work` keyword rather than for the keyword
itself. The patch is not a boolean at that path, so the server rejects or
ignores it and the tag never lands.

Route the keyword patch sites - setKeyword, removeKeyword, migrateKeyword
and the category-tab batch patch - through `keywordPointer`, which escapes
per RFC 6901 the way the calendar store already does for participant ids.
Flat keywords are untouched, so `$seen`/`$flagged` and friends keep the
exact pointers they had. The dev JMAP mock reads the escaping back, or
nested tags would behave differently against the mock than against a real
server.

migrateKeyword also took any answer as success: a method-level error on
the query read as "no message carries this keyword", one on the write was
ignored outright, and a per-message refusal was invisible - so a rename
that moved nothing still reported a clean migration. It now answers with
{ migrated, refused }, because the work is batched and a failure partway
through cannot undo the batches already applied. A whole batch the server
refuses stops the rest, which it would refuse too; a single message it
refuses does not, the mail behind it being unaffected. A query error
throws, nothing having been written yet, and so does a run that migrated
nothing at all - the case where the caller must not rename a tag over
messages that all kept the old keyword.

The walk over tagged mail was not paginating safely either. It stopped at
the first page shorter than the limit it asked for, but RFC 8620 section
5.5 lets a server clamp that limit, so a clamping server left most of the
mail unmigrated. Positions are also only comparable within one queryState,
which the walk ignored: mail arriving mid-walk shifts every later page. It
now restarts when the state changes under it, and gives up - before any
write, so the caller can simply repeat - once a mailbox has moved under it
three times, rather than mixing ids from states that no longer agree. A
page carrying no id it has not already seen ends the walk, which keeps it
bounded against a server that answers every position with the same page.
2026-08-18 08:15:22 +05:00
Paulhenry Saux e2f24e017f fix(plugins) : divide PRF workflow (getOrCreate) into two for better compatibility 2026-08-17 21:43:58 +02:00
zisisvakras 816cd1bf56 test(jmap): cover the Email/set keyword removal patch shape
Extend the existing keyword write coverage to pin the three call sites
that clear a keyword. markAsRead, batchMarkAsRead and toggleStar must
patch with null and never with false, and must still send true when
setting the keyword.

The three removal cases fail against the previous boolean form, one per
call site. The three set cases pass either way, since that path was
already correct.
2026-08-17 15:26:41 +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