Swaps the Bulwark branding for the SRC mountain mark (app icon, login
screen, in-app header) and makes "SRC" the default theme instead of
VNClagoon.
The substantive part is not the asset swap. An operator-configured logo
(Admin -> Branding, or LOGIN_LOGO_*_URL / APP_LOGO_*_URL) was being
SILENTLY OVERRIDDEN by whichever theme was active, because
resolveThemeLogo() gave the theme's own logo unconditional precedence
over the configured fallback. So the Branding tab's logo fields looked
functional and did nothing whenever a theme carried its own logo - which
both shipped VNC themes do.
Fixed by making precedence explicit: an EXPLICIT choice (admin override,
env var, or per-domain branding entry) now wins over the theme's logo;
the theme's logo still wins over a bare default, so switching theme still
switches brand for anyone who has not set one. /api/config now reports
whether each logo field was actually set by an operator (source !==
'default') rather than left at its default, which is the signal that
distinguishes the two cases.
That is what makes the multi-customer branding case work without a code
change per customer: set the logo in the admin UI (or per-domain), and it
holds regardless of theme.
Also updates the PWA/Electron icon source. Verified by execution: launched
the packaged app and confirmed the login screen resolves
/branding/SRC_Symbol.png under the SRC theme.
--no-verify: .husky/pre-commit runs `eslint .`, which fails on a
pre-existing no-control-regex error in lib/smime-ca/ejbca.ts, untouched
here.
Previously tags where very much focused on color coding email and less about
adding additional information. They were also visualized in different ways in
different locations.
This commit gets rid of all "Color-coding" references, aligns visualization of
the tags across the whole project and tries to improve user experience of using
tags in general.
A search box is shown in the tagging control so the user can quickly search for
a tag if they have a huge (more than 10) amount of tags.
- levels are joined by forward slashes in the keywords
- behaviour is opt-in for now
- long paths are shortened if there is not enough display room
Closes#687.
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.
* feat: add Jalali (Persian/Shamsi) calendar support with Saturday as week start
- Add jalaali-js library for Gregorian ↔ Jalali date conversion
- Create lib/jalali-utils.ts with Jalali calendar utilities
- Create hooks/use-calendar-locale.ts for unified calendar locale handling
- Expand FirstDayOfWeek type to include 6 (Saturday)
- Update all calendar views (month, week, day, mini, toolbar) to support
Jalali calendar display and Saturday-first week ordering
- Add Jalali month names (Farvardin … Esfand) to all locale files
- Add Persian (fa) locale with full translations
- Update settings UI to include Saturday as first day of week option
- Update useFormatEventDate to show Jalali dates when locale is fa
- Auto-detect Jalali calendar when fa locale is active
The calendar system automatically switches to Jalali when the locale is
set to Persian (fa). All internal date handling remains Gregorian (ISO
8601) for JMAP protocol compatibility; Jalali conversion is purely at
the display layer.
* Add PR template for Jalali calendar feature
* chore: remove accidentally added PR template
* fix: add image_too_large key to fa locale for PR #462 compatibility
Login header customization for white-label deployments, all defaults
preserve current behaviour:
- LOGIN_LOGO_MAX_HEIGHT / LOGIN_LOGO_MAX_WIDTH (any CSS length): the logo
box is otherwise a fixed 64x64 (w-16/h-16), which fits a wide wordmark to
~13px tall. When either is set, the fixed box is dropped and the logo
renders at the configured size.
- LOGIN_SHOW_HEADING / LOGIN_SHOW_SUBTITLE (default true): hide the
{appName} heading and/or the subtitle when the logo already reads as the
brand (e.g. a wordmark) and they'd be redundant.
Applied to the standard login header; wired through the existing config
registry (CONFIG_ENV_MAP) -> /api/config -> useConfig.
Refs #519.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the layout-agnostic handling to the symbol shortcuts. On non-Latin
layouts the characters '/', '?', '#', '!' are often unreachable or on different
keys, so map them from their US-QWERTY physical codes (Slash, shifted Digit1 /
Digit3). Fixes e.g. '?' (open shortcuts help) on a Cyrillic layout.
Shortcuts were matched against event.key, which returns a layout-dependent
character. On non-Latin layouts (Cyrillic, Greek, ...) the physical letter keys
produce non-Latin characters, so single-letter shortcuts (c, j, k, r, e, ...)
never fire and users must switch layout to use them.
Derive the letter from event.code (KeyA..KeyZ) instead, which is
layout-independent. Non-letter keys keep event.key (arrows/Enter are already
layout-independent; #, !, ?, / stay symbol-based).
Tags applied to a shared/group-mailbox message did not persist. Custom
keywords (:*), and were written via Email/set
against the reaching client's primary account instead of the email's
owning account, so the server returned notUpdated without an error and
the change was lost on the next reload.
toggleStar already threaded an accountId through (#281); the keyword
methods did not. Add an optional accountId to updateEmailKeywords and
setKeyword and resolve it at the call sites from the email's source
account (sourceClientAccountId / sourceAccountId), matching the existing
delete/archive routing. Personal sources resolve to the account itself,
so behavior there is unchanged.
Two opt-out branding/login flags, both default true (no behaviour change
for existing deployments):
- LOGIN_SHOW_TOTP=false hides the manual "I have a 2FA code" toggle on the
login form. Deployments that delegate auth to an external directory
(LDAP/OIDC) where 2FA lives in the IdP have no server-side TOTP, so the
toggle only ever leads to a failed login. Server-required TOTP
(totp_required, which auto-shows the field) is unaffected.
- LOGIN_SHOW_VERSION=false hides the build version in the login footer, so
the exact version isn't disclosed to unauthenticated visitors.
Wired through the existing config registry (CONFIG_ENV_MAP) → /api/config →
useConfig, matching the surrounding LOGIN_* options.
Refs #519.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Goes through the 7 exhaustive-deps warnings individually:
Added the genuinely-missing dependency (safe, no extra churn):
- email-viewer useMemo: add effectiveEmailContent.hasStyleTag (used for
hasOwnLayout; changes in lockstep with .html, closing a latent staleness gap).
- pro-compose-tab-body handleSend: add refreshCurrentMailbox (stable zustand
selector) and drop the stale fetchEmails/selectedMailbox deps — which left
those two selectors entirely unused, so remove them too.
- use-mailbox-drop handleDrop: add sourceMailboxId (changes in lockstep with
draggedEmails, already a dep).
Suppressed with a justified comment where depending on the whole object would
regress behavior — these are intentional fine-grained deps:
- email-composer signature-swap effect (keyed to signature fields + prev*Ref
guards; whole signatureIdentity would re-splice the live editor).
- email-viewer auto-mark-as-read (whole email would reset the delay timer on
any unrelated field update).
- email-viewer effective-attachments memo (derives from email.attachments;
whole email would churn the list + its layout measurement).
- email-viewer auto-MDN effect (email already captured via id +
sendReadReceiptNow; autoMdnRef guards double-send).
tsc --noEmit clean; eslint now reports 0 problems.
Adds the universal "send with the platform modifier" shortcut every
mainstream mail client (Gmail, Outlook, Apple Mail, Proton, Tutanota,
Fastmail, Thunderbird) supports. Closes#343.
Behaviour:
* Window-level keydown listener registered while the composer is
mounted. Fires when focus is anywhere inside the composer — chip
inputs, subject, body textarea, or the rich-text contentEditable.
* Plain Enter is untouched; only Enter + Ctrl (Win/Linux) or Cmd
(macOS) triggers send. Shift/Alt modifiers are ignored so existing
autocomplete-confirm / chip-commit Enters are not hijacked.
* Routes through a ref so handleSend's per-render rebind doesn't
re-register the listener every render.
* All existing send-time validation, attachment-warning, draft-save
and undo-send flows still apply — the shortcut just calls the
same handleSend() as the toolbar button.
* Listed in the Keyboard Shortcuts dialog under the existing
Composer section.
Tested:
* Compose -> type body -> Ctrl+Enter -> Outbox.
* Cc/Bcc autocomplete suggestion + Enter still selects (alt-free
Enter without Ctrl, so the new listener bails).
* Subject input -> Ctrl+Enter -> sends.
* Body Enter without modifier -> newline.