Fixes from the review of api.files:
- `files.attach` to a new draft runs `onBeforeAttachmentUpload` for each
file and the server's total size limit before `compose.open`, which
runs neither; refused files come back in `refused`
- a write is refused when a folder above the target is missing from the
listing, instead of counting it as not shared
- `onFileMove` reports the item as it was, from where it was, like the
Files app's own move
- writes are counted against the 20-a-minute limit before the drive is
listed, so refused writes count and cost no listing
- reads stop the download past 20 MB (`fetchBlobArrayBuffer` with a
limit) and answer `too_big` when the listed size was wrong
- when the server refuses the new content of a replaced file, the old
version's copy is taken out of the trash again
The two getText assertions on `fetchBlobArrayBuffer` now expect the
account (undefined for the own drive) and the byte limit, since the
call passes both.
api.email.getThread fetches only the newest 50 messages of a thread
(Thread/get, then Email/get for those ids) instead of all of them with
their bodies. api.email.getThreadCount answers the number of messages
from Thread/get, with its own rate limit.
plugin-host-thread.test.ts now expects getThreadEmails to be called with
{ newest: 50 }, the new option that limits what is fetched.
- The button and the panel of Calendar, Contacts, Files and Settings
show only when the plugin's shouldShow says yes, so a plugin switched
off for these views (admin, user or no service) leaves no button that
opens an empty panel, and no extra rows in Contacts and Settings.
- Files: while the details are open the panel steps aside there only;
the other views keep it as the user left it. With both stored open the
details fold when the page loads.
- Resizing stops where the view keeps its 480 px, so a drag or the arrow
keys no longer fold the panel away (and the handle keeps the focus).
- Settings and Contacts render the same element with or without the
panel, so a form keeps what was typed when the panel starts docking.
- Contacts on a phone: the panel button sits over an open contact too.
- The panel buttons of the reader and the views have one name, "Plugin
panel", with aria-pressed, and name the plugin's panel shortcut in
their title; the open panels are named landmarks.
The reader tests look the button up by its new name and pressed state
(viewer-toolbar-over-side-panel, reader-plugin-surfaces,
viewer-plugin-panel-width), since "Show/Hide plugin panel" is gone.
api.host.features.sidePanel now says whether this plugin may offer the
app-side-panel (ui:side-panel granted), not only that the webmail has
it. A plugin enabled before an update added the permission can then say
so instead of offering a switch that does nothing. The host computes the
assistant's flags per plugin for a frame's init and for each push.
plugin-host-features.test.ts: the host bridge assertions take the
assistant's flags as the test plugin sees them (it lacks ui:side-panel,
so sidePanel is false there).
The AI assistant's "more actions" need host calls today's plugin API
lacks. Area c of lib/plugin-sandbox/assistant adds them:
- `calendar.respond(emailId, status)`, new permission calendar:respond:
answers an invitation of the user's own account through
lib/calendar-rsvp.ts, like the reader's banner
- `calendar.deleteEvent` now returns an undo token for a single event;
`calendar.undoDelete` creates it again from the host's copy
- `filters.list/create/undo` over Settings > Filters' model; forward,
redirect, reject, discard and keep are refused, and so are folders
that are shared or not the account's own
- `email.batch/undoBatch`: up to 200 messages through the store's
batch functions, restore points first, one entry in the undo history
- `vacation.get` also says whether the out-of-office forwards mail
- `app.describeSettings`, `app.setSettings` and `app.undoSettings`, new
permission settings:change, over the allow-list in
lib/settings-schema.ts, refusing locked, hidden, restricted and gated
settings when the change is made
Undo tokens stay in the host for 30 minutes, bound to the plugin and
the account. app.getSettings now shows every setting setSettings may
change. settings:write's text is reworded to what it gates (tags).
plugin-host-api-app-info gains a test; its other assertions are as
they were.
The sandbox runtime copied only the three policy flags from the host's
features, so a flag an assistant area adds (sidePanel, files, ...) never
reached api.host.features and the plugin hid the feature. The runtime
now copies every other flag the host names as true or false.
The host bridge assertions compare against withAssistant(...) like the
snapshot ones already did, because the init payload and host-change
carry the assistant's flags on top of the three policy flags.
EmailReadView.attachments gain cid and alt for images the HTML shows
inline. plugin-projection.test.ts now expects the cid on its inline
logo, since the view carries it.
api.email.getAttachmentText reads PDF (pdf.js), Word, Excel, PowerPoint
and OpenDocument files (own reader over fflate and DOMParser, unpacking
capped at 50 MB), text, CSV, HTML, calendar and address files, and
answers with a status for what it can't read. The extraction lives in
lib/attachment-text so Files can use it too.
The sandbox runtime now copies every flag the host sends into
api.host.features; before, it kept only the three core flags, so the
assistant's flags never reached a plugin. The host bridge assertions in
plugin-host-features.test.ts compare against the core flags plus the
assistant's (withAssistant), since the init payload and host-change now
carry attachmentText too.
The assistant's new host calls come in four areas (context, actions,
Files, the panel in every view), built side by side. Each area gets its
own files under lib/plugin-sandbox/assistant for its permissions, method
table, dispatcher, host feature flags and sandbox methods; the shared
files only read the merged result. No area adds anything yet. The host
features test now compares against the core flags plus the areas' flags.
The review of offline mode found ways to send a message twice, to lose
unsent mail without a word, and to send mail without its signing,
encryption or check plugin. This fixes them and the smaller findings:
- each outbox message is taken for sending in one database transaction
under a Web Lock per account; only a failure before the submission
request (SendNotStartedError from the client) puts it back, any other
one marks it "may have gone out"; read-backs after the submission no
longer fail a send; a send of a closed tab turns uncertain after a minute
- every sign-out the user didn't confirm names the deleted messages on
the sign-in page, or in a toast when another account stays signed in
- nothing is queued while an enabled plugin isn't running (after a start
without the network none can); the composer says why
- the outbox waits for a fresh policy and holds messages the rules have
since made stricter (a required label, a required send check)
- another tab switching offline mode off sticks: only switching it on
may create the database
- only attachments of kept messages are kept, filed with their message,
deleted with it and under the cap between syncs; file names and types
are sealed; folders that went away leave the scope, and a folder that
couldn't be asked keeps its mail without holding the others back
- a group scope that forbids offline mode waits until the account's
groups are known; one server down no longer holds another's mail
- an offline start only when the server is really unreachable, and the
real sign-in follows by itself once it answers
- the service worker files pages and chunks under the build that served
them, and the shell list of another build isn't kept under this one
- a raw send counts as the composer's only from the plugin running its
takeover, for that identity
- errors and refusals are in all languages; the banner tells "offline"
from "server can't be reached"; outbox buttons name their message;
changes and messages go in the order they were made
Settings > Backup & reset gets "Keep mail on this device", off until the
user turns it on in this browser. While it is on:
- the JMAP client hands the server's answers to an encrypted cache in
IndexedDB (AES-GCM, non-extractable key) and answers from it when the
network is gone: the folder list, the last 30 days of the Inbox and of
the folders the user opened, bodies and attachments, at most 250 MB,
oldest out first; a background sync keeps it current
- read, flag, tags and moves made offline are queued and sent in order
when the server answers again; deleting for good and emptying a folder
are refused offline
- the composer, quick reply and the S/MIME and PGP plugins' sends that
complete the composer's go to an outbox once every step up to the
transport ran (checks, label, signature, plugin send checks); only the
finished send waits, with files attached offline
- the service worker keeps this build's app shell, so the app also
starts offline from the kept session and the last policy
- a full sign-out, the idle sign-out and signing one account out delete
it, a sign-out the user starts asks first when mail is unsent, and the
login page names what an automatic sign-out deleted; a policy fetch
that forbids it (devices.noOffline) wipes that account
Not in Lite.
- The message for send checks is built after waiting for required
checks that are still starting: a send right after sign-in gave them
no external recipients and no file types.
- A plugin's forward is held too while a required check is down, also
with no plugin listening.
- A plugin the admin forces on that hasn't reached this browser yet (a
new device, a failed plugin sync) counts as starting.
- A check that stopped working says to reload or tell the admin.
- The files that were checked are the ones sent, and recipients a
plugin adds after the check are checked again.
- A file Bulwark couldn't read is marked unreadable; a text file
starting with "MZ" is text, Java and Android apps are told from ZIPs.
Changed assertions: runPluginSendCheck takes a builder and reports
guard-loading or guard-failed instead of guard-down; the composer test
of an auto-disabled guard expects the stopped_working text; the sniff
tests give programs a zero byte, as real ones have, and expect
unreadable on a file that can't be read.