Commit Graph
14 Commits
Author SHA1 Message Date
honzupandLinus Rath 334fdbfb86 feat: show unread count badge on favicon
Closes #560.

Composes the active inbox's unread count over the base favicon as an SVG
badge, served as a percent-encoded data: URL, so new mail is visible on a
tab that is not focused — including when the browser collapses tabs to
icon-only, where a title-based count disappears entirely.

The base icon is read from the rendered <link rel="icon"> rather than from
config, so admin and per-domain branding overrides are inherited for free:
the count is drawn on whatever logo the deployment actually serves. Keeping
the badge in SVG rather than rasterising to a canvas also means the browser
can rasterise it at whatever size it asks for, so a HiDPI tab is not served a
16px bitmap.

Notes on the approach:

- The badge link is an *additional* icon link that we append and mark as
  ours; we never remove or mutate a link we did not create. Next's metadata
  icons are rendered by React, which keeps a fiber pointing at that DOM node,
  so removing it would leave React holding a detached node and throw
  "Cannot read properties of null (reading 'removeChild')" on the next
  commit that deletes the fiber. Appending instead means the last-declared
  icon wins, and non-SVG fallback links survive with their type/sizes intact.
  (The usual recipe for this feature — assign canvas.toDataURL() to the
  existing link's href — does both of the things that break here.)

- Every change of state is an *insertion* of a fresh link of ours, never a
  mutation or a removal, because that is the only signal a browser reliably
  re-reads the favicon on. Firefox ignores an in-place href change, and it
  equally ignores a removal — so clearing the badge by deleting our link left
  a stale count painted on the tab until a hard reload. Clearing it instead
  inserts a new link of ours carrying the original base href.

- Holding last place has to be defended: on a client-side navigation React
  re-hoists its metadata icon link into <head>, landing after ours, and the
  base icon silently wins again. A MutationObserver on <head> moves our own
  link back to the end whenever a foreign icon link appears — moving only our
  node, never anyone else's. It no-ops once ours is last again, so a move
  cannot feed itself.

- The badge is a full-width band across the foot of the icon, drawn to the
  metrics measured from Gmail's own 16px favicon: band height 0.625 of the
  icon, digit cap height 0.44, flush to the edges, corners rounded by about a
  pixel. Full width is what keeps a three-glyph label legible — rounded ends
  waste exactly the horizontal space it needs. Neutral white with black digits
  rather than the conventional red: faviconUrl is admin-overridable and
  Bulwark's own icon is rgb(219,45,84), so a red badge sat red-on-red.

- The base SVG may be admin-uploaded, and the branding route deliberately
  serves it under a sandboxing CSP because SVG can carry script. Re-emitting
  it as a same-origin data: URL would un-fence that, so script, foreignObject
  and every on* handler are stripped before serialising.

- Mounted in the root layout, not on the mail route: the badge belongs to the
  tab, so mounting it on the page would clear it on every hop to settings,
  calendar or contacts.
2026-07-16 18:00:41 +02:00
Shuki VakninandLinus Rath 22418c17cf feat(i18n): Hebrew locale + full RTL support 2026-07-08 00:07:34 +02:00
honzupandLinus Rath 7db6fd7e24 feat: add setting to disable tag-color row tint in message list
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).
2026-07-07 23:39:05 +02:00
Linus Rath e141abc849 feat: add option to hide total message count on folders (#498) 2026-06-28 20:31:35 +02:00
Linus Rath cd247e9c47 Merge remote-tracking branch 'origin/main' into feat/all-mail-cross-account-views
# Conflicts:
#	stores/settings-store.ts
2026-06-24 16:05:00 +02:00
Stefan HildebrandtandLinus Rath 751f3c1685 feat: per-account All Mail folder selection
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).
2026-06-24 15:52:09 +02:00
Stefan Hildebrandt a29c33b50a feat: cross-account "All accounts" views + full group/shared-account support
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.
2026-06-23 19:16:22 +02:00
Linus Rath c51c3655d5 feat: add "All Mail" view 2026-06-15 18:42:23 +02:00
Linus Rath 537707d9ed feat: include group inboxes in unified mailbox view #328 2026-05-23 16:01:22 +02:00
Linus Rath b75bbaa517 feat: auto-redirect to Pro shell when proInterface is on 2026-05-21 15:47:05 +02:00
Linus Rath 98879802ae feat: add Pro interface 2026-05-18 19:17:39 +02:00
Linus Rath 1d09f5a623 feat: align Focused list preview with other layout previews 2026-05-11 15:41:25 +02:00
Linus Rath 2c513129f2 feat: add Reading Pane at Bottom mail layout #262 2026-05-11 15:35:43 +02:00
Linus Rath 3f36045990 feat: reorganize settings into 6 groups with clearer tabs 2026-04-21 22:22:02 +02:00