X, Escape, clicking another message and Escape in a pop-out window
now all save the draft and close, with a "Draft saved" toast that has
Discard (with Undo). The Save or discard question shows only when that
save fails. A Reply or Forward closed before the user changed anything
leaves no draft. Undo send being on by default no longer asks "Send
without a delay?" on servers without delayed sending; only a delay the
user picked does.
Changed test assertions: the close-guard, priority, account-switch,
threading, sign-out, mention, draft-attachments, discard-uploads and
save-pending-attachments tests reached saving or discarding through the
Save or discard dialog on close, which now only appears after a failed
save. They now close directly, or use the Discard button, or check the
failed-save question. The send-delay test marks its delay as chosen.
The autocomplete, chip-drag and paste tests found chips by their old
"Name (address)" text; they now find them by the "Name <address>"
tooltip, which is where the address went.
Tests changed: the "asked once" tests reached the second check through
a second dialog, which no longer exists, so they now stop the send at
"Send this message?" instead. The empty-body test asserted the error
under the editor; an empty body now asks instead of blocking Send.
The first version of quick compose skipped `onBeforeCompose`, a composer
moved back could end up open in both windows, a reply moved back under
Pro flagged the selected message, and a crashed pop-out came back blank.
- A new message in a pop-out runs `onBeforeCompose` with the same result
as the main window: a plugin that stops it closes the window.
- The way back has two steps, so a pop-out that gave up can't leave a
second owner. A handoff behind a plugin's gate makes it say busy.
- A Pro tab made from a composer moved back answers its own message.
- Each save writes `draft`, `account` and `owner` into the pop-out's
address, which a reload after a crash starts from. What was typed since
the last save, up to the 2 s autosave delay, is still lost.
- The recorder refuses keys that type a character on this keyboard, and
what the shell refuses from a page, and says why.
The desktop app and an installed PWA had a row across the whole window
with the title and the window controls. That row is gone. The rail,
sidebar, list and reader run to the top of the window in their own
colours, and the controls sit in the top row of whatever is rightmost.
- `lib/window-controls-overlay.ts` finds and marks those rows before
paint, and they reserve the controls' width through
`--titlebar-right-inset`
- the rows and the rail are the drag area, everything clickable is
`no-drag`, and an invisible band lets a page with no row still move
- settings and the admin shell get a row only under the overlay, Pro's
tab strip and Mountain View's bar match the controls' height
- Alt floats the menu over the top left, in `window-menu-bar.tsx`
- skeletons carry the same marks, so nothing shifts when the app loads
A plugin's render can be a key operation, and mark-read replacing the
message object ran it twice: a second PIN dialog, and two cancels lock the
key. The effects now depend on what the hook sees and on the explicit
re-render nonce, not on the object. The list row and the full message still
differ, as a row has no headers for a plugin to recognise its mail by.
onRenderEmailBody can return `blockExternalContent: true` (flag
`api.host.features.blockExternalContent`) for a body it can't vouch for.
External content then stays off whatever the settings and trusted senders
allow, in the reader and in conversations, with no way to load it. The S/MIME
plugin uses it against EFAIL.
The lock is off by default, and an OS policy can require it and set the
timeout. It locks after a time without use counted across all windows and
tabs, on OS screen lock in the desktop app, and from Lock now.
Locking saves open composers, stops the outbox at its next message, closes
the store and stops its worker with every key, then reloads the page, so
nothing of the old page stays in reach. The new page holds the app back at a
Suspense boundary until someone unlocks. Pop-outs get no session while
locked, push notifications name nobody, and subjects leave plain storage.
One random lock key wraps each account's key as a layer. The PIN wraps the
lock key with PBKDF2 at 600,000 iterations, with a wait after wrong tries
that a reload doesn't reset. A passkey with PRF and the desktop's OS storage
wrap it too. Details and limits are in the decisions file.
A restored composer opened whatever the id named. Sending keeps the id, so its autosave would have destroyed the Sent copy, Send mailed it twice and Discard deleted it. It now needs the login in its address and a real draft, keeps its From with several accounts, and stays on the list if the server doesn't answer. A message from a bulwark:// link no longer marks itself read or sends a receipt. Native menus speak all 30 languages. Spellcheck dictionaries come from Google only after a yes, never in no-third-party mode.