Message-list rows are tinted with the first tag's color, which becomes
overwhelming when a label applies to most messages (for example
per-account labels). Add a `tintListRowsByTag` setting (default true, so
current behavior is unchanged) with a toggle in Settings beside
"Colorful Sidebar Icons". When off, rows are not tinted; tag dots and
chips still show the color. Gated in both list renderers
(email-list-item and thread-list-item).
Replaces the global allMailFolderIds (string[] | null) with a per-account
Record<accountId, string[]>, so each account chooses which of its own folders
the "All Mail" view merges. A missing entry = "not configured" (defaults to
every no-role folder); an explicit [] = "no folders".
- settings-store: type/default -> Record (default {}); persist version 4 -> 5,
migration drops the legacy global list (the active account isn't known at
migrate time); onRehydrate + importSettings coerce/ignore any non-record
(legacy global string[] | null) shape. isPlainRecord() guard.
- email-store.resolveAllMailJmapIds: reads the entry for the account the view is
scoped to (viewingAccountId ?? activeAccountId); undefined -> all no-role,
[] -> none.
- layout-settings: read/write the active account's entry; when more than one
account is logged in, an italic hint names the account the selection applies
to (settings.appearance.all_mail.account_hint, 19 locales; de/ro translated).
- Test: stores/__tests__/settings-store-all-mail.test.ts (per-account
independence, explicit-empty vs not-configured, importSettings legacy guard).
Per review: gate the return-to-list behaviour behind a setting,
returnToListAfterAction, defaulting to true (the Gmail/Yahoo default). When off,
deleting the open message keeps the previous auto-advance-to-next behaviour.
Adds the setting to the store (persisted), a toggle under Reading settings, and
i18n keys across all locales (English; non-English need translation). The same
setting will govern mark-as-unread (#468).
Add cross-account aggregate mail views and make group/shared (delegated)
accounts first-class in every aggregate view. (The unified mailbox, the "All
Mail" view, and "include group inboxes" already exist on main; this branch adds
the cross-account views and the shared-account correctness work.)
New views (admin-gated + per-user toggle, nested under Unified Mailbox):
- Cross-account "All accounts": All unread / All starred / All mail across every
connected account, including shared/group folders. Each list labels the source
folder of every message.
Source reference on aggregated emails (the core of the shared-account work):
- Replace the overloaded `accountId` with two explicit, always-set fields:
`sourceClientAccountId` (the login the mail is reachable through) and
`sourceAccountId` (the owning JMAP account). `accountId` stays display-only.
- Resolution is branch-free everywhere: pick the client by sourceClientAccountId,
pass sourceAccountId as the JMAP accountId (no-op for personal), read the
owner's mailbox list cached by JMAP id. No capability scan.
Shared/group-account correctness across all aggregate views:
- Route open (click + auto-fetch), thread/conversation open + reply-refresh,
mark read, star, move, delete (account-scoped trash), archive (owner-routed
createMailbox / fetchAccountMailboxes), and spam + undo via the source ref.
- Add accountId params to toggleStar / batchMarkAsRead / batchDeleteEmails /
createMailbox where missing.
- Fix local unread/total counter math for shared folders via emailInMailbox()
(matches namespaced shared ids and bare own ids).
- Keep the unified/cross virtual selection on background mailbox refresh (no
jump back to inbox after deleting in All Drafts/Junk).
Junk UX:
- In "All Junk" the spam action becomes "not spam" in the viewer, context menu,
and list hover icons; undo routes shared mail back to its own inbox.
Admin:
- Policy gates crossUnread/Starred/AllViewEnabled, each noting the matching
per-user toggle (allMailViewEnabled clarified too).
i18n / docs / tests:
- locales (19): cross-view labels + descriptions and hover not_spam, translated
in all shipped languages.
- FEATURES.md + README.md document the new views and group-account support.
- Tests for shared-account routing (single + batch + undoSpam), decoration, and
unified-selection preservation.
Bulwark had no read-receipt support (JMAP/Stalwart have no native MDN).
End-to-end, client-side, in three parts:
- Request (compose): a toolbar toggle (MailCheck, green when on) sets
Disposition-Notification-To on the outgoing message via the JMAP
"header:<name>:asText" create property. Threaded composer -> page ->
email-store -> client.sendEmail. Default from requestReadReceiptDefault.
- Detect (viewer): reads Disposition-Notification-To case-insensitively from
the parsed headers and shows a banner (green Send / red Ignore) in the
unified notification bar. Hidden in Sent/Drafts/Trash/Junk and once handled.
message/disposition-notification + message/delivery-status report parts are
filtered out of the attachment list.
- Respond (MDN): lib/mdn.ts builds an RFC 8098 multipart/report (text/plain +
message/disposition-notification, UTF-8/base64, localized subject + body).
client.sendReadReceipt uploads the blob, imports it into Sent via
Email/import, then submits with an explicit envelope. Both Send and Ignore
set the $MDNSent keyword (RFC 3503) so no client re-prompts. Behaviour
configurable: ask / always / never.
New: lib/mdn.ts, read-receipt-banner.tsx. Settings (requestReadReceiptDefault,
readReceiptResponse) + UI. All 17 locales.
* Added account selection for protocol links when multiple connected accounts are available, including mailto: links
* Added support for handling mailto: links in an already-open PWA/session instead of always opening a new tab
* Added webcal: protocol handling for calendar links
* Added account selection for webcal: links when multiple calendar-capable accounts are connected
* Added an import-or-subscribe choice for detected webcal calendars
* Added protocol handler settings for registering mail and calendar handlers and choosing the open mode
* Added service worker/session coordination for passing protocol requests between browser/PWA contexts
* Added tests and translations for the new protocol handler flows
Makes every client-side fetch('/api/...') call respect the mount prefix
when Bulwark is served behind a reverse proxy at a sub-path (e.g.
`/webmail`).
### Problem
`getPathPrefix()` (added in 1.4.13 by #XXX / d762b94) already fixes
router navigation and redirect URIs for reverse-proxy deployments.
Client-side `fetch()` calls, though, still target the browser origin:
await fetch('/api/foo')
// Browser at /webmail/en/inbox → hits /api/foo (not proxied → 404)
That means the login flow, session establishment, settings save, plugin
loader, calendar import, etc. all break the moment you front Bulwark
with nginx (or any proxy) at a sub-path.
### Fix
Add `apiFetch(input, init)` next to `getPathPrefix()` in
`lib/browser-navigation.ts`. It prepends the mount prefix to any
absolute path at call time:
await apiFetch('/api/foo')
// /webmail/en/inbox → /webmail/api/foo
// /en/inbox → /api/foo
Same runtime-detection model as `getPathPrefix()` — the built bundle
works at any mount point without rebuilding or env-var config.
Protocol-relative (`//cdn...`) and absolute (`https://...`) URLs pass
through unchanged. Server-side route handlers are untouched (the mount
prefix is a browser-only concept).
### Migration
Mechanical rewrite of every client-side `fetch('/api/...')` call in
hooks/, lib/, stores/, components/, app/ — 99 call sites across
26 files. `route.ts` handlers and other server-only files are skipped.
### Compat
- No behaviour change when mounted at `/` (the common case): an empty
prefix + raw path is identical to raw path.
- No new config knobs, env vars, or build flags.
- Supersedes PR #181 (which required a build-time `NEXT_PUBLIC_BASE_PATH`)
— will close#181 after this lands.
### Testing
Should run the existing suite; smoke-tested by Jabali Panel which
reverse-proxies Bulwark at `/webmail/` (https://github.com/shukiv/jabali-panel).