The admin skeleton test and the outgoing-hosts path test now expect the
sidebar heading "Users" and the page "Features and settings": the
Organisation group was renamed and User policy moved into it.
- After a new rule is saved in Settings > Filters, a notice offers
"Apply to N existing messages in Inbox" when the rule would change
mail already there (the same check and batch actions as rules made
from a message).
- "Stop processing" is no longer an action. The checkbox "Don't apply
later rules" does it; an old rule with the action opens with it
folded into the checkbox, and what came after the stop (which never
ran) is dropped.
- "matches regex" and the raw Sieve editor show only with advanced
settings on (the editor stays for a script only it can change).
- "Match all conditions" / "Match any condition", no capitals.
- The folder picker names folders as the sidebar does, indented, and
leaves out Drafts, Sent and Scheduled unless the rule already uses one.
The regex test now turns on advanced settings first, and the focus
tests open the raw editor with advanced settings on, since both are
advanced now.
The #1327 store test now serves its optional plugin with defaultOn false:
it checks that every plugin is listed when IndexedDB fails, and a plugin
the admin didn't force is on by default now.
Offline sat in the middle of Backup & reset, privacy settings were spread
over Reading, Composing and Message list, and the version card took room
from the page list in the sidebar's footer. The search matched letters
inside words ("lock" found Time format and Send Checks) and knew no other
words for a setting ("offline", "autoreply", "logout" found nothing).
- Offline is a page in the top group: Keep mail on this device, what is
kept, Remove everything from this device, and the Outbox as its own
section. Old `/settings/backup/offline` links and the offline notice
land there.
- Privacy holds remote images and trusted senders, read receipts, sender
pictures from the web, the desktop app's usage info and crash reports,
and a link to the push relay on Notifications. Reading, Composing and
Message list keep a link row; old links to those sections land on
Privacy.
- About is the last page and holds the version card; the footer keeps the
Advanced switch.
- The search matches whole words and word starts (substrings only in
Chinese, Japanese and Thai), and each row can carry synonyms in the
user's language. A page matches by its name and keywords or when one of
its rows has every word. Account gets a Sign out row so "logout" leads
somewhere.
- The index test now also fails when a page draws a section the index
doesn't know, and a new test fails on two indexed rows with the same
name. Tests that rendered remote content and sender pictures on Reading
and Message list render Privacy instead, since that's where the rows are.
The usability pass of 2026-11-04 found the General and Notifications
pages hard to read, and the desktop app showing browser-only rows.
- Time zone: a searchable list by city and offset, common zones
first, with today's city names (Kolkata, Kyiv, Ho Chi Minh City).
The stored value stays the IANA id the runtime knows.
- First day of the week: "First day of the week in calendars".
- Desktop app: no browser link handlers (the app has its own "Default
mail app" row), no usage info on General (it moves to Privacy),
time zone and app lock texts without the browser and tabs.
- Taskbar flashing and quiet hours move to Notifications.
- "Windows" is now "App windows".
- Notifications: "Sound for new mail", "Sound for reminders", a
"Sound" section with a "Tone" row, "Notifications when Bulwark is
closed" with "Turn on for this device", a short badge description.
- Link handlers: "Open email links in Bulwark" with "Use Bulwark for
email links", no "Technically" sentence.
- App lock: "The PIN stays on this device.", the protection text
behind "What does this protect?", "1 hour" and "4 hours", one line
in the PIN dialog instead of repeating its title.
- Usage info names "the app's version numbers", as the desktop's
first run does, instead of the Node version.
Test changes: the telemetry wording test looked for "Node version",
which the text no longer names; the search-anchor test now fills in
{appName} before comparing, as the search index does, because row
labels name the app.
The plain-language test asserted that the normal strings show while the switch is off and that changing the switch swaps them live. With no switch left, it now asserts the plain strings show from the first render and through a language change.
Out of office used grey placeholder text, so switching the reply on sent
an empty message. A reply never written now opens with an editable subject
and message in the user's language; the switch stays off while the message
is empty and says "Write a message first", and a reply that is on can't be
saved with its text deleted. Forwarding won't switch on without an address,
and the address field shows while it is off. The page puts the switch
first, then "Reply to" with plain choices, then "From/Until (optional)",
then one message editor with formatting instead of a text box plus a
second "Formatted message (HTML)" box. A message nobody edited is sent as
the server has it. A failed save is no longer retried once at once when
the last change was a click.
Test changes: the forward tests type the address before switching
forwarding on (it no longer switches on without one) and check the new
order and option labels; the HTML-body and template tests use the one
editor instead of the text box and HTML switch, which are gone. The
search index names the new rows.
Where people may sign in to a server of their own (the custom endpoint
setting and the desktop app's standalone mode), the sign-in form asked
for a "JMAP Server" first, refused a host without https:// with the
browser's own bubble and talked about CORS. Now the email address comes
first and the server is looked up from its domain (JMAP well-known, SRV,
then the admin's default) through a new budgeted route. The "Mail
server" field opens only when nothing is found, takes a bare host, and
keeps the CORS hint behind "Having trouble?" (not in the app). Gmail,
Outlook.com and iCloud addresses get a plain note that these offer no
JMAP. The app's browser button stays the main one and asks for what it
needs under the field it needs.
Also on the page: the theme button is an icon named "Light or dark",
the heading is the app's name only when the admin gave one (else "Sign
in", without the subtitle repeating it), "I have a code from my
authenticator app", "Add account", and no 11 px text.
login-page-i18n.test.tsx now names the app for the subtitle test and
waits for the heading, since the default name no longer shows a subtitle,
and finds the theme button by its new name.
The review found the store key readable from a copy of the disk. The
desktop app only added its OS layer with the lock on, the record from
before a lock stayed in LevelDB and opened the mail with no PIN, any
frame could unwrap the PIN record and guess at it, a lock left the key
in the renderer's memory, and a store waiting after a refused login went
to whoever signed in next under the same username@host.
- The desktop app wraps the store key with safeStorage, lock or no lock.
A record without the layer gets it at its next open. A refusal is said
out loud, and only a computer with no key store goes without, which
settings say. So do they for a browser, which has no such layer.
- A record that gained layers, and every record after a changed PIN, asks
for a new key: `sqlite3_backup` into a new file under a new key while
the store keeps working, one write to the record, then the old file
goes. A changed PIN makes a new lock key, since the old PIN's record
stays on disk too. The passkey and OS unlock go with the old key.
- `wrap` and `unwrap` come through `claim()`, and the key under the PIN
takes a secret from main, one counted trip per guess.
- A lock on the desktop ends the renderer rather than reload in it.
- A sign-in compares the waiting store's JMAP account and server first,
and wipes on a mismatch. The account id keeps its form: every store,
setting and server record of an account is keyed by it.
- The 30 days also run in an app that stays open.
Changes an existing assertion: turning the lock off now keeps the
safeStorage layer, by decision, so that test expects it to stay.
The shell now takes a message's signed attributes where it took a digest,
so a page can't get a login or TLS digest signed. A quiet option lets a
plugin answer from what's allowed, for anything the user didn't ask for.
An admin's install no longer covers the certificates on someone's own
computer: a managed plugin gets `keystore` when that person says yes, also
when an update adds it, and it's forgotten once everyone has signed out.
Changed test assertions: the fixture plugin needs a grant now, as managed
alone is no longer enough, and the bridge gets an options argument.
A wipe by name took any username and server with no session behind it. It
now needs a sealed cookie the account got while it was signed in, since a
dropped account has no session cookie left. The old wipe test asserted that
a name alone was enough, which is the behaviour being removed.