Review follow-up on the shared-folder mutation routing.
The owner lookup replaced the resolved client with any connected client whose
JMAP account id matched. Account ids are opaque per-server values, and personal
mailboxes carry an accountId too, so this ran on every folder mutation and could
hand a mutation to a client of a different server that happens to issue the same
id - where the bare mailbox id may exist and belong to someone else. Keep the
resolved client when it already is the owner, and require a candidate to match
the server URL as well as the account id.
Recover the owner account and the bare mailbox id from the namespaced store id
when no mailbox object is available. Previously the raw `accountId:id` went to
the server as a mailbox id with the owner dropped, so the mutation targeted the
active personal account; in setMailboxRole that cleared the personal role before
failing.
Drop the shared account header context menu when the account carries no usable
id. buildMailboxTree groups shared mailboxes without an accountId under the
placeholder "unknown", which cannot be a mutation target.
Review follow-ups on the unclosed-angle split:
- The paste path fell back to splitMailbox for every entry, which keeps the
last angle run only and folds everything ahead of it into a display name.
`Ann <a@x.com Bob <b@y.com` became one chip and a@x.com disappeared without
even landing in `invalid`. Take that path only when the prefix is not an
address itself.
- The new separator branch ignored `inGroup`, so an unclosed `<` inside RFC
5322 group syntax shattered the group its sibling branch keeps intact.
- angleRunCloses now scans quote-aware, so a `>` inside a quoted display name
no longer counts as closing the run, and it starts past `from` so the helper
is correct whether it gets the separator or the opening `<`.
The suite intermittently failed "settingsSync ... SESSION_SECRET_FILE" under
full parallel load (passed in isolation). Two causes:
- env vars were mutated directly on the shared process.env, so other test
files in the same worker could leak in. Use vi.stubEnv + unstubAllEnvs so
each test's env is isolated and auto-restored, and stub the managed vars to
undefined in beforeEach for a clean baseline.
- both SESSION_SECRET_FILE tests wrote a shared relative ./session-secret and
removed it with a fire-and-forget async unlink; under load the pending
unlink from the first test fired during the second (after write, before
read), so the file was missing. Use a unique temp file per test, removed
synchronously in afterEach.
Verified: 17/17, stable across repeated runs and with hostile env
(APP_NAME/SETTINGS_SYNC_ENABLED/SESSION_SECRET) set in the real environment.
The "This event only" edit path built nested JMAP patch pointers
(recurrenceOverrides/<recurrenceId>/<field>). On the first edit of an
occurrence the override object doesn't exist yet, and JMAP pointers can't
create a missing intermediate object, so Stalwart returned notUpdated and
the edit failed with two error toasts.
Build the override as a single pointer recurrenceOverrides/<recurrenceId>
whose value is the occurrence's field set (dropping identity/series keys),
mirroring the working "delete this occurrence" path. Extracted as the pure,
tested helper buildRecurrenceOverridePatch.
Closes#774.
A calendar holding only tasks (VTODO, e.g. from tasks.org) appeared in the
event calendar UI as an empty calendar. Stalwart's Calendar/get exposes no
supported-component set, so tasks-only-ness is derived from the calendar's
objects (mirroring the existing event/task split): a calendar is tasks-only
iff it holds >=1 object and every object is a task.
- getTasksOnlyCalendarIds scans an account's objects (CalendarEvent/query +
minimal /get), early-exits once every calendar has an event, only marks
after a full scan, and fails open on the cap or any error.
- getAllCalendars/getCalendars annotate calendars with isTasksOnly.
- Such calendars stay in the store for the tasks view (task picker, task-list
grouping) but are filtered from the event sidebar and default selection.
Verified against Stalwart v0.16.16: the tasks-only calendar is flagged, the
default (with an event) is not.
Closes#761.
The self-sent handling from #703 (reply to your own thread message ->
address the original recipients, ignore Reply-To) was entered whenever
From matched one of the user's identities. Contact-form / helpdesk mail
commonly arrives From the user's own address with the real correspondent
in Reply-To, so that branch ignored the Reply-To and looped the reply
back to the user's own To.
Only take the self-sent branch when there is no EXTERNAL Reply-To: a
Reply-To resolving to a non-own address is an explicit RFC 5322 directive
and now wins even for self-sent messages (#776). A Reply-To that is only
our own address stays ignored for self-sent (#703 preserved).
Closes#776.
connectSSE() overwrote sseAbortController with a new controller without
aborting the previous one, so re-running setupPushNotifications (which
happens per connected account and on fairly frequent effect changes)
orphaned SSE streams. readSSEStream only unwinds on abort/done, so each
orphan held its HTTP/1.1 socket forever; with multiple accounts this
starved normal JMAP POSTs, which queued and timed out (~30s).
- connectSSE: abort any prior stream (+ its ping monitor / pending
reconnect) before opening a new one -> exactly one live SSE per client.
- startPollingFallback: bail if a polling interval already exists.
- checkForStateChanges: skip while a poll is already in flight
(stateCheckInFlight) so overlapping triggers don't stack POSTs.
Closes#781.