A notification tapped with the app closed reopens it on a link that names
its mailbox (`?slot=`). The browser reconnects its logins one at a time,
so that mailbox is often not connected yet when the link is read: with ten
logins, one on the Gmail bridge came back at 3.3 s and the link was judged
at 1.8 s. It was declared unavailable - "sign in to that account to open
this link" - and the app stayed on whichever mailbox was active.
Wait for the login instead, and give up only when it is not one of this
browser's logins, its restore fails, or 20 s go by.
The preview hands the attachment's blob to the browser's own viewer,
which picks the viewer from the blob's type and downloads anything it
cannot show - under the blob's UUID. It only corrected the exact
application/octet-stream declaration from the file name. Other
declarations kept the preview from working: IBM i (AS/400) systems send
every attachment as application/x-as400attachment, some senders use
application/x-pdf or force-download, and the octet-stream check was
case-sensitive.
Move the decision into previewBlobType(): a PDF, image, audio or video
preview takes the first type its viewer can show - declared, then
served, then the one the file name implies. Other previews keep the
declared type unless it is generic, and the name never yields a
script-capable type.
App store terms put limits on recipients, such as device caps and DRM,
that section 10 of the AGPL does not allow, so a Bulwark app built from
this code could not be shipped through the Apple App Store or Google
Play without breaking the licence. The mobile app reuses the webmail
locale catalogs and several ported modules, so the permission is needed
here as well as in the native repository.
Add a section 7 additional permission that allows app store
distribution as long as the source of every shipped version is public,
free, linked from the listing or the app, and not further restricted.
The permission is not final yet: not every earlier contributor has
answered the consent request. A reservation limits it to contributions
made on or after 30 September 2026 and to older parts whose authors
have agreed. The README licence line and CONTRIBUTING.md say the same.
The expand/collapse toggle referenced calendar.events.show_more and
show_less, which were missing from every locale (translations test failed),
and passed a defaultMessage option next-intl does not support. Add both keys
to all 27 locales, reusing each locale's existing email_composer.show_less
wording, and drop the unsupported option.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The all-day/tasks area in the week view took its height from
(allDayRowCount + taskRowCount) across every loaded day. Since free
scrolling loads 37 to 180 days, one crowded day anywhere in that range
blew the header up to 250+ px for every week and pushed the time grid down;
tasks were also stacked below the global all-day row count, leaving a gap
on days without all-day events.
- Stack each day's tasks directly below that day's all-day events.
- Size the area from the days in view only: track the first visible day by
date (so columns prepended while loading do not shift it) plus the number
of visible columns (7 on desktop, by width on mobile).
- Cap the area at 3 rows with an expand/collapse toggle when a visible day
has more entries.
Tests cover per-day task stacking, the 3-row cap and toggle, an empty week
next to a crowded one, and the area growing when scrolling into it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing showed where a published image or download came from, and the
standalone tarballs had no checksum at all. Anyone who could swap a
release asset could swap its `.sha256` with it.
- every published image (release, `-always`, beta/dev and Lite) gets a
signed build provenance attestation, pushed to GHCR next to it
- the Lite zips and standalone tarballs are attested as well
- standalone tarballs get a `.sha256`, like the Lite zips
- the tarball build no longer holds a write token: a separate release
job, which runs no checked-out code, checksums, attests and uploads
- the README says how to check with `gh attestation verify`
The Link Mobile App step-up marked the tab before asking the server to
start the provider round trip. When that request failed, or the user
backed out at the provider, the mark stayed, and the next sign-in's
callback in that tab went to the pairing route and failed. The mark is
now set only once the round trip starts, holds that round trip's state,
and the callback drops one that does not match.
The app refuses a JMAP server over plain http (loopback aside), yet the
desktop issued a code, reported "Signed in on your phone" once it was
redeemed, and the phone failed. Linking now stops before the step-up
with an explanation.
- An app password minted for a phone that never redeemed its code stayed
valid (no expiry, full permissions, no TOTP) and used up one of the
account's five app-password slots. It is now deleted, with its own
credential, when its grant is discarded or expires.
- Replaced codes were kept for twelve minutes each, so a session could
grow the store, and the cost of every request, without bound; only the
last three of a grant are kept now.
- A session read that failed after the credential check was taken for a
non-Stalwart server and handed the phone the account password; it now
refuses the pairing instead.