Commit Graph
4542 Commits
Author SHA1 Message Date
Linus Rath e0abc7fa18 chore: merge the reviewed store, pop-outs and desktop core into the settings branch
Eviction now goes by the base's signal: `storeLost` and the `store-reset`
notice, with `bulwark-store-opened` as the marker and `prune-files` for files
with no key. The storage page and the lost count hang off that. What a user
picks is stored under `bulwark-offline-scope`, the key `offlineScopeFor`
reads, and `email-snapshot` follows the base's rule. `OfflineEnabled` is read
from the desktop bridge when it's there.
2026-11-03 10:40:47 +01:00
Linus Rath eb365938a2 test: line the merged branches' tests up with the store's wipe rules
The token route now answers 503 when the provider can't be reached, in every build, and a refused renewal for a background account now removes it as evicted, which keeps its data unless the server said invalid_grant. Two assertions from the desktop and pop-out branches change for that. Also drops a duplicate import from the same merge.
2026-11-03 10:26:00 +01:00
Linus Rath 6e262bf1c5 feat: move offline mode onto the encrypted store, with a merged outbox and conflicts
# Conflicts:
#	app/api/auth/token/route.ts
#	lib/offline/offline-mode.ts
#	stores/account-store.ts
#	stores/auth-store.ts
2026-11-03 10:21:34 +01:00
Linus Rath 84c73ab15a test: let the store's own sync finish before failing the outbox lookup
With contacts and files in the store a pass runs longer, and the failure meant
for the lookup could go to the sync's own Email/query.
2026-11-03 10:19:28 +01:00
Linus Rath 19f6bb8a07 test: find the store reset notice once, not also in the screen reader region 2026-11-03 10:11:31 +01:00
Linus Rath e8696094e4 fix: open an account again in the owner tab after another tab cleared it 2026-11-03 10:05:43 +01:00
Linus Rath 1dde703993 fix: list files again when the real client takes over from the local copy 2026-11-03 09:53:04 +01:00
Linus Rath 7dc4c775cd fix: don't wipe a store just because this window can't open it, and don't send a stand-in when a file's upload is held again 2026-11-03 09:45:03 +01:00
Linus Rath 87b15b12f6 test: check Keep offline in the Files app 2026-11-03 09:28:31 +01:00
Linus Rath fb40944c2d feat: hold kept files to the scope at once, and count them towards what each account takes 2026-11-03 09:28:31 +01:00
Linus Rath 9dd6e61d35 test: read the store's raw files without an owner, lose a key for real, and lose only the submission of a send 2026-11-03 09:25:08 +01:00
Linus Rath ebec09212e fix: say plainly when a private window, old stranded mail or a silent server is the problem, and clean the domain of a legacy Message-ID 2026-11-03 09:25:07 +01:00
Linus Rath 024078c597 fix: clear files held for nothing a week after a flush, leaving what other parts of the store still hold 2026-11-03 09:25:06 +01:00
Linus Rath 3dc2fc2c3c fix: give up on a kept attachment only when the server says it won't come 2026-11-03 09:25:05 +01:00
Linus Rath f2aedd2948 chore: merge contacts and files into the settings branch 2026-11-03 09:22:04 +01:00
Linus Rath 340ad27e32 fix: send waiting mail when a client connects after the first flush, and come back to stale sends and servers that were down 2026-11-03 09:18:42 +01:00
Linus Rath 9b3bbc6070 fix: delete a store file whose key is gone, say that it happened, and stop waiting on an owner tab that doesn't answer 2026-11-03 09:18:41 +01:00
Linus Rath b4d43d9bbe test: check what a device keeps on both servers, down to the raw bytes 2026-11-03 09:17:57 +01:00
Linus Rath 18efb9f4c3 fix: wait 30 seconds before looking up a send, bound the waits at a start, and keep opened files only as the chosen scope allows 2026-11-03 09:00:52 +01:00
Linus Rath b001cbf109 feat: show what each account keeps on the device and let it be changed and cleared
The offline section of Settings > Backup & reset now shows, per account, the
space each part takes, the last sync, a first sync's progress with the time
left, the window and attachment choices, and clearing the account. All
conflicts, calendar and contacts too, are listed there and counted in the
banner.

- the organisation's caps beat the user's choice and the desktop default, and
  every path that takes a scope goes through `effectiveScope`
- the desktop app keeps mail without asking, says so once, and stays off for
  an account where the user turned it off
- `navigator.storage.persist()` is asked on turning it on, and a refusal
  warns with a way to ask again; Safari gets the 7-day hint when not installed
- a store the browser deleted is noticed, says what was lost and syncs again
- a full disk says so, with what to do
- those who had offline mode keep it, with the 30 days they had
- Keep offline in the Files app's menu
2026-11-03 08:57:41 +01:00
Linus Rath 62a47434e3 fix: ask before a sign-out about everything unsent, including changes, and say when the local store won't open 2026-11-03 08:56:30 +01:00
Linus Rath 22a2d9e3a6 fix: keep subjects out of localStorage for accounts that keep mail encrypted 2026-11-03 08:56:25 +01:00
Linus Rath a7c3c5b9a6 feat: prune kept mail at once when the scope tightens and fetch it back when it widens
The store took a scope only when it opened and acted on it only at a full
sync, so a lower cap left old mail on the device until something else
forced a resync. Now the engine applies a changed scope first, without the
network, and once a day as the window moves on.

Also tell the page whether an account's file was there when it opened, so
it can notice a store the browser deleted.

- `calendar-sync.test.ts` now syncs with `days: null`: its old events test
  checks the calendar's own two years, and the calendar follows the scope now
- `page-side.test.ts`: the fake worker answers an open with what it found,
  as the real one does
2026-11-03 08:56:24 +01:00
Linus Rath 3b1fa089aa test: give the stand-in desktop bridge its version and platform 2026-11-03 08:54:05 +01:00
Linus Rath cdda75d13b fix: keep queued changes and mail through rate limits, logins that need renewing and proxy errors, and show the server's copy after a real refusal 2026-11-03 08:49:53 +01:00
Linus Rath 2b48047bcc feat: merge the webmail side of the desktop app
# Conflicts:
#	lib/desktop/types.ts
#	locales/ar/common.json
#	locales/ca/common.json
#	locales/cs/common.json
#	locales/da/common.json
#	locales/de/common.json
#	locales/en/common.json
#	locales/es/common.json
#	locales/et/common.json
#	locales/fa/common.json
#	locales/fr/common.json
#	locales/he/common.json
#	locales/hu/common.json
#	locales/it/common.json
#	locales/ja/common.json
#	locales/ko/common.json
#	locales/lv/common.json
#	locales/mn/common.json
#	locales/nb/common.json
#	locales/nl/common.json
#	locales/pl/common.json
#	locales/pt/common.json
#	locales/ro/common.json
#	locales/ru/common.json
#	locales/sk/common.json
#	locales/th/common.json
#	locales/tr/common.json
#	locales/uk/common.json
#	locales/vi/common.json
#	locales/zh-TW/common.json
#	locales/zh/common.json
#	stores/account-store.ts
2026-11-03 08:48:10 +01:00
Linus Rath 074d42e62a fix: stop keeping a plain mail list for accounts in the store and drop a stale answer from a replaced client 2026-11-03 08:43:36 +01:00
Linus Rath 55ca12adbd fix: say when a start stays on the local copy, queue its sends, paint it only with folders kept and fill the From list after the swap 2026-11-03 08:43:35 +01:00
Linus Rath 512422addd feat: merge title bar, pop-out windows and saving attachments to a folder 2026-11-03 08:37:29 +01:00
Linus Rath b79781cc94 fix: never lose a draft moving between windows and keep credentials off the window channel
Review of the pop-out work found ways to lose text, a wrong sign-out and
changes reaching ordinary browser tabs.

- Drafts: a closing window hands text over only to a main window holding
  its login, and asks instead when the last save failed, the browser is
  offline, a save is running or plugins could still have a say. A failed
  rescue goes to "Continue draft". The opening window lets go only once
  the composer is on screen, also for Edit draft in a message window.
  Send options travel with the draft. Closing during a send asks first.
- Sessions: answers are encrypted for the window that asked, and only a
  window holding the login answers. A renewal queued on the lock can be
  called off by a sign-out, signs nobody out once its account is gone, and
  takes the cached token after waiting (Rauthy `nbf`, #552).
- Ordinary tabs run none of the host. No lock is held for life.
- A message window hides after a refused session and waits for its
  folders; a composer stays on screen with its text. Another tab dropping a
  login it couldn't restore no longer closes pop-outs.
- Plain-text mail stays selectable in the desktop app. Folder save renames
  reserved and shell names and skips one refused name. All display-mode
  checks know the overlay. The attachment bar keeps to one row.

The earlier test that accepted text being gone after a failed rescue now
asserts that it reaches the "Continue draft" slot.
2026-11-03 08:37:10 +01:00
Linus Rath 706550daf4 fix: keep local mail and the outbox when an automatic sign-out wasn't a real refusal of the login, for 30 days at most 2026-11-03 08:33:03 +01:00
Linus Rath 10a2c99b83 fix: keep syncing when a message has two keywords differing only in case, and compare keywords the way the server does 2026-11-03 08:22:15 +01:00
Linus Rath c76ba0fee2 feat: let organisations cap what devices keep offline 2026-11-03 08:11:43 +01:00
Linus Rath 46766ac8aa fix: let a server build pass its guard, and let the desktop app save drafts and forget a server on sign-out 2026-11-03 07:57:35 +01:00
Linus Rath f2d1b54c3e test: catch the forbidden notice whenever the policy arrives in the offline spec 2026-11-03 07:43:51 +01:00
Linus Rath a365ba9041 Merge branch 'feat/desktop-offline' into feat/do-desktop-core 2026-11-03 07:34:32 +01:00
Linus Rath 2ebb5ea9cf test: run the handoff wipes on the local store 2026-11-03 07:31:50 +01:00
Linus Rath dccf4e8b9f chore: merge feat/desktop-offline into the windows branch 2026-11-03 07:31:32 +01:00
Linus Rath b1c4bb00d9 chore: merge opening files and sharing into the mail branch 2026-11-03 07:30:08 +01:00
Linus Rath 81248d9a33 test: keep asking for the policy in the offline spec, since the list now paints before the app's own fetches 2026-11-03 07:29:45 +01:00
Linus Rath fcbc6edb89 fix: stop losing the first local store call of a tab that just became the owner, which left it syncing for good 2026-11-03 07:17:23 +01:00
Linus Rath 0dcabc9a7c test: type the desktop flag test's environments 2026-11-03 07:13:18 +01:00
Linus Rath d6ae3d172b feat: merge opening files and sharing into the installed app
# Conflicts:
#	components/mail/mail-app.tsx
#	lib/__tests__/sw-harness.ts
2026-11-03 07:11:39 +01:00
Linus Rath 33c0335843 docs: say how a pop-out composer saves its draft on close 2026-11-03 07:09:55 +01:00
Linus Rath f716ea66ee fix: ask before a share starts a message and save nothing until the first edit
Any site could POST a form to the share target on load: with no clicks
the composer opened with its subject and file, and autosave stored a
real draft on the server 2 seconds later. The worker can't see where a
POST came from, and Chromium's link capturing accepts form submissions,
so an installed-app window proves nothing either.

- every share landing now asks first, in every window and browser; a
  text landing address gets the same question. Nothing goes up before
  the answer.
- handed-in text and files are the composer's starting state: no
  autosave, page-close save or sign-out flush before the first real
  edit.
- the worker ignores frames, keeps at most 5 entries and 100 MB for 15
  minutes, sweeps on activate, on each share and from a page timer,
  and seals what it keeps with a key passed in the landing fragment.
  Offline wipes clear it too.
- the server fallback uses the fragment and caps by encoded length:
  3000 Japanese characters made a 27 KB `Location` and a 431.
- a second compose handoff waits until the composer closes, also one
  set aside by reading a message, where it used to replace the first.
- a launch leaving the mail page saves what was just typed first.
- an expired handoff or a missing stored file now says so; shares get
  the control-character stripping and limits of `parseMailto`.

Existing tests changed because the landing moved from the query to the
fragment, stored entries gained a sealed part, and a share now asks
first.
2026-11-03 07:08:38 +01:00
Linus Rath d3bd93b80d feat: send calendar edits with the outbox and use kept files offline 2026-11-03 07:02:40 +01:00
Linus Rath 20b2e7054e chore: merge search, notification actions and calendar into the mail branch 2026-11-03 07:02:39 +01:00
Linus Rath 28bc4d976c test: cover conflicts, an emptied Trash and a lost send response in the offline spec 2026-11-03 06:55:40 +01:00
Linus Rath 7ac582f803 fix: compile the desktop trust path out of every other build, and fail a build that keeps it 2026-11-03 06:55:22 +01:00
Linus Rath bb2393cf04 feat: open messages and drafts in their own windows and save all attachments to a folder
An installed app had no title bar of its own, no way to save attachments
except a zip, and "pop out" stayed inside the page. A new window was a
second copy of the app that had to restore the session itself, and on
start-up that evicts any login made without "Remember me" from the list
every window shares.

- Title bar: `display_override` with `window-controls-overlay`, one
  `--titlebar-inset` variable at the page roots and skeletons, and a drag
  strip with the window title. 0 without an overlay, so nothing moves.
- Save all to a folder: `showDirectoryPicker`, asking the device policy
  before the picker and before each file, with the zip as the fallback.
- Pop-out routes `/popout/message/<id>` and `/popout/compose`, on offer in
  an installed app or when the desktop bridge has windows. Hidden in Lite.
- A pop-out borrows the main window's session and never evicts or starts a
  token timer. Every renewal now takes a Web Lock, so two windows can't
  send the same refresh token, which Stalwart 1.0 may treat as a replay.
- A draft moves to its window under the same id. A closing window hands
  unsaved text to the main window to save, since its own request wouldn't
  survive the close.
- Read state, drafts, theme and language follow across windows, and
  `html[data-desktop]` gates the app-like behaviour in `app/desktop.css`.
2026-11-03 06:50:21 +01:00