Commit Graph
146 Commits
Author SHA1 Message Date
Linus Rath 2e262b9d5c fix: title the browser tab after the Pro tab in view #1186 2026-10-07 19:34:19 +02:00
Oğuz YılmazandClaude Opus 5.5 8ca7dbbe9b feat: name the active account in the browser-tab title
The tab title was "<context> - <app>", so two mailboxes open side by side
read the same in the tab strip and in the browser's session-restore list.

It is now "<context> - <account> - <app>", the order Gmail and Outlook Web
use: browsers cut a long title from the end, so the part that changes comes
first and the app name last. There is no setting for it; the account is what
tells the tabs apart.

The formatting, and the mail view's choice of context (composer, then
message, then mailbox), live in lib/tab-title.ts with tests. The mail view
sets its title through useDocumentTitle(), which other surfaces can use too.
Next renders the root layout's metadata <title> again on every client-side
navigation, after the surface has mounted; the hook puts its title back when
that happens. Only the surface that set the title last does so, so two
titles never fight.

Refs #1159

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:26:23 +03:00
Linus Rath 6e5c5390df Merge pull request #1162 from dealerweb/fix/focus-trap-rerender
fix: keep the focus in dialogs while the page behind renders again
2026-10-06 23:28:51 +02:00
Linus Rath 0b4d0b0f3c feat: limit the day and week views to working hours and days #1164 2026-10-02 22:56:16 +02:00
Linus Rath 0e39b199a1 feat: link the running build's source from the About card #1161 2026-10-02 22:10:29 +02:00
dealerweb 0c376dd0e4 fix: keep the focus in a dialog's field while the page behind renders again
useFocusTrap started over whenever its onEscape changed, and callers pass
a new function on every render. Each start moved the focus back to the
opener and then to the dialog's first element, so a field lost the focus
whenever the page behind rendered again: every 30 seconds through the
connection check, or when new mail updated the folder counts. The trap
now reads the latest onEscape from a ref and starts once per opening.

A view change in a dialog (a list giving way to a form) can remove the
element that had the focus. The restart used to pull it back by
accident; the trap now does that on purpose, and only when the focused
element was removed.
2026-10-02 12:42:40 +02:00
Linus Rath 29c74c3b77 feat: sign in with an access token 2026-10-01 00:24:47 +02:00
Linus Rath 770fb2c84a fix: resolve trash and archive in a background login's own folders #1132
The folder-list cache from #1132 published a reused list only when it
was freshly fetched. When the Pro folder tree had loaded a login first,
the unified build reused that list and never stored it under the
login's JMAP id, so trash and archive for that login's mail were
resolved against the active login's folders until it pushed a change.
A reused list now fills any key nothing has published yet, without
overwriting a live one.

The Pro folder tree also marked a login as loaded before its first
fetch finished and never retried a failure, which left that login's
tree empty until an account switch. A failed load is now retried on
the next run, as the identities hook already does.
2026-09-30 20:35:13 +02:00
Linus Rath 2d2b0753be Merge pull request #1132 from lucamzanon/perf/multi-account-startup
perf: stop the startup request storm with many logins
2026-09-30 20:33:32 +02:00
Linus Rath cd14807133 Merge pull request #1112 from theLucius7/fix/config-load-recovery-20260927
fix: recover from transient application configuration failures
2026-09-30 20:32:44 +02:00
lucamzanon 612dcc81ee perf: stop reloading every login's folders and identities on each connection
Two effects keyed on the account list reloaded every connected login each
time the list changed - and while a browser restores its logins it changes
once per connection, so the cost was 1 + 2 + … + N:

- useProMultiAccountMailboxes, which the Pro shell runs for its per-account
  folder tree, reloaded every login's folders: 55 Mailbox/get with ten logins.
  It now loads each login once per client, through the same cache as the
  unified scope, and reloads all of them once on an account switch.
- useProMultiAccountIdentities reread every other login's identities: 45
  Identity/get. Each mount now reads each account once; what is already
  known shows at once, and a read that outlives the run that started it
  still reaches the dropdown.

Both are keyed on the set of connected logins instead of `accounts`, which
also changes for display-name and login-time updates.
2026-09-30 19:04:49 +02:00
Linus Rath 7f5728fea2 Merge pull request #1101 from dealerweb/fix/wopi-base-path
fix: send the WOPI status and launch requests under the base path
2026-09-30 16:46:11 +02:00
Linus Rath 1531dae7eb feat: create filter rules from the message right-click menu 2026-09-30 13:52:41 +02:00
Linus Rath 9d140fa762 Merge pull request #1052 from lucamzanon/feat/account-row-colours
feat: colour unified rows by account and give every tag a hue of its own
2026-09-29 23:30:24 +02:00
Lucius7nya d923b92034 fix: recover from transient application configuration failures
Retry interrupted or invalid runtime configuration requests with bounded backoff and per-attempt timeouts. Validate the complete response before caching it, and prevent stale HTTP-cached configuration.

Cover request and body timeouts, shared hook consumers, retry bounds, the server response contract, and both Lite configuration paths.
2026-09-27 15:41:21 +08:00
Linus Rath daf67850d2 fix: reply from the receiving account's identity in the unified inbox #1104 2026-09-26 12:31:29 +02:00
Linus Rath 3823d2da17 fix: sign in and show addresses on internationalized domains #1100 2026-09-24 21:15:22 +02:00
dealerweb 3eb9ade572 fix: send the WOPI status and launch requests under the base path
Behind a reverse proxy at a sub-path (NEXT_PUBLIC_BASE_PATH, e.g.
/webmail) both WOPI requests used a bare fetch('/api/wopi/...') and
reached the mail server instead of the webmail. The status probe came
back 404, so office editing always looked unavailable there - and since
#1047 the probe runs for every opened message, logging the 404 each
time. Route both through apiFetch like every other API call, and cover
the sub-path mount with a test.
2026-09-24 21:08:17 +02:00
Linus Rath 7e5b996ee6 feat: open office attachments in mail read-only in the document editor #1047 2026-09-23 19:01:47 +02:00
lucamzanon 42b6cc08b0 feat: colour unified rows by account and give every tag a hue of its own
In the unified view each row can be shaded with its account colour
instead of carrying an account dot, behind a layout setting. Tags keep
their own generated hue on their chips.
2026-09-19 05:44:44 +02:00
Linus Rath fdfcb1711e feat: add Bulwark Lite build for Stalwart Applications 2026-09-19 00:53:53 +02:00
Linus Rath 2d4a562432 fix: harden Bulwark Lite login, deep-link replay and settings gating 2026-09-18 22:16:57 +02:00
Linus Rath 5de98bc04b feat: add Bulwark Lite static export build 2026-09-17 23:54:33 +02:00
Linus Rath ee4763d5c0 Merge pull request #1036 from dealerweb/fix/calendar-principal-fetch
fix(calendar): stop the account principal fetch from failing on every load
2026-09-17 22:03:26 +02:00
Linus Rath 2e88a0c830 Merge main into fix/calendar-participants
Resolves the two conflicts in components/calendar/event-modal.tsx created
by #967 (RSVP controls), both keep-both:

- #967's canRsvp memo and #986's resolveContactName hook are both kept;
  they are independent and were simply inserted at the same point.
- #967's RsvpBar component is kept, and StatusBadge takes #986's
  signature ({ status, t }) since #986 drops the isOrganizer prop and
  updates both call sites to match.
2026-09-17 21:54:08 +02:00
Linus Rath 168a1c7844 fix: never loop forever when deduping drag-out .eml names #1039 2026-09-17 21:42:16 +02:00
dealerweb bc5302568d fix(calendar): wait for the JMAP client before loading the account principal
The calendar loads the principal's alias addresses so an alias-organised
event is recognised as the user's own. The fetch fired on mount, but after
a page load the JMAP client is restored asynchronously: the call failed
with "Not authenticated" in the console, and the once-only guard then
blocked the retry for the rest of the session, leaving the aliases empty.

Move the logic into useAccountPrincipalEmails, which waits for the client
and fetches once, and cover the sequence with a hook test.
2026-09-15 12:44:36 +02:00
Linus Rath 15a5497a12 feat: freely scrolling month, week and day views #759 2026-09-09 16:38:55 +02:00
Linus Rath 12416d9c47 fix: wire the x shortcut to thread expansion #683 2026-09-07 22:34:47 +02:00
Linus Rath 928e2e3794 feat: add a server-side switch for the Stalwart JMAP passthrough #904 2026-09-07 22:34:47 +02:00
ibayueandClaude 6f99c017a9 fix(calendar): resolve bare-address participant names across alias domains
The organizer of a self-created event still rendered as a bare address
(#738 follow-up): the event stores the organizer under the server's
node domain while the name lives elsewhere — the account/principal
name, the identity From-name, or a contact card on the account's
primary domain. Exact contact matching alone never hit.

useContactNameResolver now resolves in layers, first hit wins:

1. Contact card holding the exact address (curated data outranks all).
2. Contact card with the same local part on a *related* domain — one
   domain extends the other at a "."/"-" label boundary
   (node-example.com vs example.com, mail.example.com vs example.com).
   Applied only when the related cards agree on a single name.
3. The account display name (live principal "Full name") for the
   user's own addresses.
4. The identity From-name (a one-time snapshot of the same, so it only
   serves when the account layer had nothing).

Name sources that just echo the address itself are skipped — they add
nothing over the bare address renderers already show.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-07 16:48:26 +08:00
ibayueandClaude 8cbc5bc539 fix(calendar): dedupe participants, resolve contact names, show organizer status
Three bugs when viewing an event's participants (#738):

1. Phantom participant. Stalwart can emit the organizer both as the
   ORGANIZER-derived participant and as an ATTENDEE line, so the same
   address rendered twice — "4 participants" for three people.
   getParticipantList() now folds same-address entries into the
   first-seen one (real RSVP beats needs-action on merge), and
   getParticipantCount() counts addresses so event-card and agenda
   badges agree.

2. Bare email for a known contact. The organizer's display name is
   dropped on Stalwart's iCalendar round-trip, so the organizer
   rendered as a bare address despite having a contact card with a
   备注 name. New useContactNameResolver() hook fills missing
   participant names from the contact store (fill-only — event data
   stays authoritative), threaded through the panel, the hover
   popover, the RSVP view, and the organizer name written on save.

3. Organizer status. The organizer with no explicit status read as
   待处理; they owe no reply, so they now display as accepted, and the
   panel's status badge no longer repeats the organizer label the
   (组织者) suffix already carries.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-07 16:48:26 +08:00
KazNIISA IT e7d3782344 feat(pwa): badge the installed app icon with unread count (Badging API)
Adds useAppBadge alongside the existing useFaviconBadge, mounted from the
same FaviconBadge component and gated by the same faviconUnreadBadge
setting. Targets the separate icon Chrome/Edge/WebKit show for an
installed PWA (taskbar/dock/Home Screen/pinned-app-shortcut), which does
not react to favicon changes at all - a merely pinned browser tab is
unaffected, only an actually installed app.

No unmount cleanup: the badge is scoped to the installed app/origin, not
to this component or window, so clearing on every unmount would blank
another still-open window's accurate badge. A count of zero or the
setting being off already clears it through the main effect.
2026-09-03 11:37:36 +05:00
Linus Rath bc3b6a0eb6 fix: collapse search hits for the same server object reached through several logins #641 2026-09-01 22:15:49 +02:00
Linus Rath c075b79cbb feat: global search palette and search tab in the Pro shell #641 2026-09-01 20:55:13 +02:00
Linus Rath 0db6bfdc64 feat: global search core: query parser, ranking, cross-account providers #641 2026-09-01 20:55:12 +02:00
Linus Rath ca7fbbe416 feat: WOPI office document editing in Files (Collabora/OnlyOffice/EuroOffice) #425 2026-08-31 23:16:19 +02:00
Linus Rath 4fe5701b3b feat: default sidebar apps for all users #931 2026-08-29 12:31:17 +02:00
Linus Rath 9f32fc879b feat: add custom time zone setting overriding browser detection #755 2026-08-25 19:18:16 +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
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 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
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 d9f9ed10cd fix: add embedded custom app origins to CSP frame-src #787 2026-08-16 23:48:58 +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 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 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 28c5876a0c Merge pull request #809 from hildebrandttk/fix/identities-consistent-namespacing
fix(identities): namespace all accounts consistently in the Pro composer
2026-08-13 23:58:12 +02:00
Stefan Hildebrandt 44cdd083a3 fix(identities): namespace all accounts consistently in the Pro composer
Follow-up to the calendar/contacts consistent-namespacing fix. The multi-account
identity aggregation left the ACTIVE account's identity ids raw while namespacing
the others, so an identity id changed form when the active account changed. Now
every aggregated account is namespaced (`<accountId>::<id>`), matching the
calendar/contact stores.

Safe: the composer already resolves the raw id + owning client via
stripCrossAccountIdentityPrefix everywhere it hits JMAP (currentIdentityRawId /
composerClient), reply-identity resolves by email, and preferredIdentityIds is
applied against the raw identity store (unaffected). No consumer relies on the
active account's identity id being raw. Single-account mode (hook disabled) keeps
the store's raw ids.

Full unit suite + typecheck clean.
2026-08-13 21:31:50 +02:00