Selecting a tag does not clear an active search: `handleTagSelect` leaves
`searchQuery`/`searchFilters` alone and `selectKeyword` leaves
`selectedMailbox`, so a tag view and a search are active together. The
paths then disagreed about which one wins:
fetchEmails / refreshCurrentMailbox → `selectedKeyword` first, so the
tag fan-out ran and the query was dropped;
searchEmails / advancedSearch / loadMoreEmails → the search first, so
since #1082 the all-folders fan-out ran and the tag was dropped.
Both orders are defensible on their own; having both is not. A
push-triggered refresh silently replaced a search result list with the
whole tag, and "load more" appended rows the tag does not contain — the
same class of inconsistency #1082 set out to remove between a search and
its own next page.
Resolve it the way the codebase already documents ("a tag view takes
precedence over the selected folder"), and keep the query: a search inside
a tag view now narrows the tag, in all five paths. `fetchTagEmails` grows
an optional `extraFilter`, which `getEmails` already accepts alongside
`hasKeyword` — the same argument the message-list category tabs use — so
no new query plumbing is needed.
Searching within the tag is also the more useful reading: a tag view is a
list the user deliberately opened, and a search box above it reads as
"within this", not "forget this".
Remove packages nothing imports any more (`@tiptap/extension-image`,
`asn1js`, `pkijs`, `pvtsutils`, `sonner`, `webcrypto-liner`) and the
`elliptic` override, which only applied through `webcrypto-liner`.
`sharp`, `undici`, `@tiptap/extension-paragraph` and
`@tiptap/extension-heading` were imported directly but only resolved as
transitive dependencies of next, jsdom and starter-kit. List them
explicitly and point the `undici` override at `$undici` so both stay in
step.
`selectAccountMailbox` switches the viewed account but kept
`searchMailboxId`, which names a folder of the account being left.
Since #1082 that scope resolves against `resolveActionMailboxes()`, which
now returns the NEW account's mailbox list, so the id no longer matches
anything. `searchEmails` / `advancedSearch` then take the picked-folder
branch with `mailbox === undefined`, fall back to `jmapMailboxId =
searchMailboxId` and `accountId = undefined`, and query a foreign folder
id against the wrong account — which comes back empty.
It is invisible in the UI: the <select> binds `value={searchMailboxId}`
and has no matching <option>, so a native select renders the first one and
the dropdown reads "All folders" while the store still holds the stale id.
The user sees an empty result list for a search they believe is unscoped.
Reset it on the account switch, so the scope always matches what the
dropdown shows.
Rolling back ran the container from the local tag, so the demo recorded
`bulwark-demo:previous` as its image — and that tag is moved by the very
next deploy. Run the id instead; the tag stays only to keep the image from
being pruned.
The demo container followed ghcr.io/bulwarkmail/webmail:latest through
Watchtower, so a release reached it silently and there was no way to see,
trigger or undo it. It is now a deployment of this repo:
- .github/workflows/deploy-demo.yml runs after "Publish Docker Image on
Release" succeeds, or on demand with a tag
(`gh workflow run deploy-demo.yml -f tag=1.10.0`, or action=rollback).
It only talks to the server; nothing is built.
- deploy/demo-deploy.sh is the forced command behind the deploy key
(/usr/local/bin/bulwark-demo-deploy): pulls the tag, recreates the
container with the demo mounts, checks /api/health and goes back to the
image it replaced if that fails. A pre-release moves no :latest, so the
deploy after one finds the same image and does nothing.
Watchtower has been recreated without bulwark-demo; it still updates the
two rbm.systems containers.
Keep the latest Docker tags on the stable release when a GitHub release is marked as a pre-release, so auto-updating deployments on :latest do not pick up beta builds.
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.
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.