Stalwart rejects a whole ContactCard over one property it does not
know, and several reached it:
- the form sent `calendarUri`, `schedulingUri` and `freeBusyUri`,
so any contact with a calendar, scheduling or free/busy link failed
to save ("Invalid property.")
- vCard import sent `source`, and Pro mode left `localAccountId` on
the card
- vCard import wrote addresses as flat `street`/`locality` fields
instead of RFC 9553 `components`
The card now passes through `lib/jmap/contact-wire.ts` on its way to
the server: client-only fields are dropped, the links are written as
`calendars` / `schedulingAddresses` (and SOURCE as a `directories`
entry), flat addresses become components, and reads map the links back
for the form and detail view.
Clearing a field (nickname, notes, a link) never reached the server:
the form set it to `undefined`, which JSON drops, and an omitted
property keeps its value. Updates now send `null` for cleared keys.
Deleting an address book that still held contacts always failed with
`addressBookHasContents`. The delete now passes
`onDestroyRemoveContents`, which the confirm dialog already promised.
Bulwark read the live display name (#900) and the account profile with
`x:Account/get`, which Stalwart only allows for admins. Every other
user got `forbidden`, so the settings page showed no name and the
identity kept the stale snapshot. The name now comes from
`x:AccountSettings/get`, the account's own settings object; aliases
and quota still use `x:Account/get` where it is permitted.
With TOTP on, Stalwart requires the current code for a password change
and for switching TOTP off ("Current OTP code is required"). Neither
dialog asked for it. Both now do, and the password change sends it as
the `otpAuth/otpCode` patch, because a whole `otpAuth` object would
reset `otpUrl` and turn TOTP off.
Saving the display name also ignored a `notUpdated` answer and showed
success.
The Sieve generator wrote actions in the order they were picked in the
rule editor. `fileinto` stores the flags set at that moment, so "mark
read", "star" or "add label" placed after a move never reached the
moved message. Flag actions are now emitted first.
Moves and copies also targeted the folder by path only. Renaming the
folder silently sent the rule's mail to Inbox. Rules now store the
folder id and emit `fileinto :mailboxid "<id>" "<path>"` when the
server lists `mailboxid` (declaring `mailbox` too, which Stalwart
requires), with the path as the fallback. Saving a rule refreshes the
path from the id, so existing rules pick up the id on their next save.
Forward actions get a "Keep a copy" option (`redirect :copy`), and the
editor honours the server's `maxNumberRedirects` (1 on Stalwart):
forward is disabled once the limit is used up, with a hint when
existing rules already exceed it, because the server skips extra
redirects without telling anyone.
Stalwart allows one active Sieve script and turns the auto-reply on by
activating its own `vacation` script. Turning the auto-reply on switched
every Bulwark filter off (including junk and folder rules), and saving
any filter switched the auto-reply off again.
When the vacation script takes over from an existing filters script,
the filters script is now re-activated with
`include :personal :optional "vacation"`, so the server-built reply
(with its date window and HTML body) runs from there. Turning the
auto-reply off drops the include again. A filters save while the
vacation script is active keeps it through the same include, and the
vacation settings read the include as "on", because
VacationResponse.isEnabled reports false in that state.
Clients that only look at VacationResponse (Stalwart's web UI, phones)
still show the auto-reply as off while the include is active.
JMAP thread ids are only unique within their account - Stalwart hands out
per-account counters such as "b" - so the cross-account views merged
unrelated conversations that happened to share an id, and every
client-side thread map (expansion, cache, loading flag, counts,
virtualizer key) mixed them up the same way.
Give ThreadGroup an account-scoped `threadKey` derived from the email's
source stamps and key grouping and all thread state by it. `threadId`
stays the raw id for Thread/get, which #281 already routes to the owning
account. fetchThreadEmailCounts now asks each source account for its own
threads instead of one client for every id. Single-account views carry no
stamps, so their key is the bare id and nothing changes there.
Non-admins usually may not read their own Account object (no
sysAccountGet), and fetchPrincipal was written to treat that as the
normal case. It only matched the word "forbidden" in the message, though,
while Stalwart answers with a `forbidden` method error whose description
reads "You are not authorized to perform this action" - so every calendar
load still logged an error and left an error message in the store.
Classify by the method error type (and HTTP 403), keep the wording checks
as fallback, and log the refusal at debug level only.
Stalwart Mail Server strictly validates JSCalendar task properties on CalendarEvent/set and rejects updates containing top-level progressUpdated with invalidProperties (RFC 8984 §5.2.2 only defined progressUpdated on Participant objects, obsoleted in JSCalendar 2.0).
- Omit progressUpdated from toggleTaskComplete in stores/task-store.ts
- Remove progressUpdated from CALENDAR_TASK_PROPERTIES in lib/jmap/client.ts
- Defensively strip progressUpdated in updateCalendarEvent and createCalendarTask
- Make progressUpdated optional in CalendarTask interface
- Add unit tests for task-store and JMAP client task methods
setSandboxLocale() (which drives api.i18n.locale for every plugin) was
fed the RAW useLocaleStore value, which defaults to '' and stays ''
under 'Automatic' language (the language-switcher's default option -
see components/ui/language-switcher.tsx, components/settings/
language-settings.tsx). setSandboxLocale() then silently no-ops on an
empty string, leaving plugins stuck on the hardcoded 'en' default even
when the app's own native UI is fully localized via browser-language
auto-detection (document.documentElement.lang / detectBrowserLocale, see
i18n/detect-locale.ts's getEffectiveLocale() - already the exact
function the app's own IntlProvider uses to resolve this same case).
Switched both setSandboxLocale() call sites in plugin-store.ts to use
getEffectiveLocale() instead of the raw store value, so a plugin's
api.i18n.locale now matches the user's ACTUAL visible language,
'Automatic' included, not just an explicit language-switcher choice.
Found while building the webdav-storage plugin's localization: native
UI showed Hungarian, but the plugin's own slotApi.i18n.locale reported
'en' - confirmed via the plugin's own debug logging, then traced from
there rather than guessed.
After a reload the unified mailbox showed only the active account's mail;
other accounts appeared only once visited. Intermittent (~1 in 14), and
not a timeout - nothing on this path uses setTimeout/AbortSignal/race.
checkAuth awaits only the active account and restores the rest in a
fire-and-forget background loop, registering each client only after
connect() resolves. buildUnifiedAccountClients drops accounts with no
live client (`if (!c) continue`). And isConnected was persisted, so it
rehydrated stale-true: the account passed the isConnected filter while
failing the client lookup, and connectedAccountsSignature already looked
complete at mount so nothing re-fired - and the effects keyed on it only
recompute counts, never the list.
Clear isConnected/hasError on rehydrate so both the filter and the
signature describe this session, and refetch the open unified/cross list
when an account joins the connected set (growth only, not on first
render, not while a search owns the list). Extracted
connectedAccountsGrew so the membership check is testable without
rendering MailApp.
Verified the reset cannot block reconnection: checkAuth reads the full
account list and never filters on isConnected.
Fixes#950
Switching folders currently keeps an active search applied and re-runs
it in the newly selected folder — deliberate behavior from #553, where
users wanted the query to follow them across folders. Others expect the
opposite: clicking Inbox/Sent should drop the search and just browse the
folder.
Add an opt-in setting (Settings → email behavior) that reconciles both.
Default is off, so #553's search-follows-you behavior is unchanged for
everyone. When on, any folder click clears the text query and advanced
filters and browses the folder instead of re-running the search.
Applied uniformly across the four folder-entry paths in MailApp — normal
mailboxes, per-account (Pro sidebar) folders, unified views, and cross
views — via one helper so no path diverges.