From e6e5e521cf538898c0fc10c6eba46effab225044 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 29 Oct 2026 05:27:26 +0100 Subject: [PATCH] docs: report the native sync with webmail 1.18.0 and Stalwart 1.0.0 --- docs/native-sync-2026-10-29.md | 187 +++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 docs/native-sync-2026-10-29.md diff --git a/docs/native-sync-2026-10-29.md b/docs/native-sync-2026-10-29.md new file mode 100644 index 00000000..cd0522c4 --- /dev/null +++ b/docs/native-sync-2026-10-29.md @@ -0,0 +1,187 @@ +# Native sync with webmail 1.18.0 and Stalwart 1.0.0 (2026-10-29) + +Branch `feat/stalwart-1.0`: native main (f358d605) merged in, then the 1.0.0 +release check, the webmail 1.10-1.18 parity fixes and the open native issues. +It is 200 commits ahead of main (181 without merges) and 0 behind. Nothing is +pushed. + +At 595df26e (the last code change; only docs follow): `tsc` clean, vitest 323 files / 4911 tests pass +(113 skipped are the opt-in live suites), `i18n:check` passes. The app has no +lint script. + +## What landed + +Merge of main into the branch: f1db6b48 (19 conflicted files). Main's later +work won (iOS push and its notification extension, SafeAreaModal, file +preview, restyle and Tabler icons, token sign-in, notification mutes, device +sync); the branch's per-account routing, session epochs and refusal handling +were carried onto it. Notable calls: jmap-client keeps the branch's +epoch-safe credential writes plus main's shared-keychain writes and +`adoptStoredTokens`; auth-store keeps the branch's `signIn` helper and routes +main's token sign-in through it; push keeps the branch's SetError-only +narrowing plus main's device-sync types and mutes. + +### Stalwart 1.0.0 (step 1) + +- Sign-in: password + TOTP on 1.0 (96ef8515: one `/api/auth` body for both + versions, RFC 7591 public client registered per server, redirect + `org.bulwarkmail.mobile:/auth/callback` because 1.0 wants a dot in the + scheme); browser sign-in with the registered client and Stalwart's data + scopes (6ffd0473, a2caa4cc); refresh-token rotation (1456e15d, 28d928f6, + cfd81737); no secret follows a redirect to another host (2c05e7bd, + 06d68d73, fe4f9211, 61654d7c); account security explains a limited client + (c6eb02d4). +- Files: draft-18 `@type`/`mediaType` (174c3fc8), listing past + maxObjectsInGet #1385 (7d8e0735), shared-folder writes per account + (18b8aa09, 7c495d03). +- Mail: inline images that 1.0 lists only in the body parts (d2bb479a), + quoted inline images in replies and forwards (913b3766), part names that + are paths (4d545f8f), scheduled mail of own and group accounts with equal + ids (15171637), bulk delete with partial refusals (547d9e59). +- Calendar #89: the branch's split CalendarEvent/get (28ade775) holds on the + release; plus mail-only shares (f3e11d4c), 0.16 loads at DST changes + (7c775571), account-switch races (1f190c94, e993540a, 41e8f005, 98dc13fe). +- Push: device-sync types retry only on a type refusal (20be1105), refused + non-public relay explained and rolled back (4ec2ccac, 90e250d3), Android + extends subscriptions in the background because both servers clamp expiry + to 7 days (9804b499, ce47c73f), subscription id reuse (77327ea8), renewal + for every account (d92a2ca8), logins sharing a JMAP account id + (9ccb6228, b352e470), removed-account teardown (152f3089). + +### Webmail parity 1.10-1.18 (step 2) + +The inventory is docs/parity/10-webmail-1.10-to-1.18.md (ec7e8e05): 112 rows, +10 P1, 40 P2. 52 are ticked with their commits (fe33dfc5); every P1 and every +P2 that doesn't need the org policy is done. Highlights: + +- Security (P1): HTML sanitizer rebuilt on a tokenizer, reader CSP + `script-src 'none'`, composer script under a nonce (f5e997ba, 07679cbf, + 47a1d249, 440a2e7a, 4e3da668); forged DMARC/DKIM chips (ef9561f1, + 8adef1e1); mailto unsubscribe (942f4517, 918571c3); winmail.dat freeze + (855d304b). +- Wrong-account writes (P1): composer pinned to its login (9daa3441, + 388f46b5, 4e57b191), undo/Send now per login (e81f0071), calendar and + contacts switch guards (see above, 6b9552fc). +- Sieve interop (P1): webmail metadata v2, address/domain rules, elsif + chains (2167a9d7, fc9c164d), plus forwarding, audience and periods + (df1a5ab4). +- P2 features: scheduled message view #84 (14d009de), group identity send + (31d8e4a4, 893f40a2), reschedule cancel-first #1308 (22f2ac5b, + fdf8eece), unconfirmed send #1019 (3eac700a), Files trash (7c914183 and + be04fcb1..946304ab), attach from and save to Files (8e07b7d8, 39e82f99), + templates in account metadata and team templates (fac318dc), shared tags + (b7b9c7e0), verification-code chip (b1550f14), unverified sender + (cc21317b), search without Spam/Trash (a412b516, 2b5257bf), calendar + search (b50270d8) and default reminders (9b87100a), feed changes reach + imported events (8a71495e, 47f29718), directory browser (0fd14a83), + rules from a message (d546d509), contact links and vCard (32f70c10, + 07fd03e2, 3c156673), placeholder warning (a8e7a3f4). + +### Issues (step 3) + +#80 96c7c045, #81 01983a38, #82 7a1031a9, #83 6f475c0c, #84 14d009de, +#86 65f00640, #87 e47c05c5, #89 (branch 28ade775, checked on the release). +#85 needs a decision (below). No issue was commented on or closed. + +Every group got a separate review agent; review findings were fixed in a +follow-up round on each branch (two HIGHs in the composer, two pre-existing +HIGHs in the HTML handling, one in calendar feed sync). + +## Verified live + +Opt-in suites in src/api/__tests__/live-stalwart*.test.ts, run at the tip +against Stalwart 1.0.0 (stw-rel, :8120) and 0.16.27 (:8016), each with its +own e2e account. All pass on both: base 17, mail 14, compose 10, files 16, +push 11, contacts 9, calendar 22, auth 14 (with a TOTP account). The base +suite now imports its own mail, so it runs on any account. + +Android emulator (Pixel 7 API 34, release build of the tip): + +- 1.0.0: browser sign-in (consent page, registered client, new redirect + scheme) as usera; reader with the new CSP (height, links open in the + browser); reply composer loads, draft saved; push enabled through the + hosted relay, a mail sent by userb arrived as a notification; calendar + loads with series (#89); Files shows the trash. +- 1.0.0 and 0.16: password + TOTP sign-in for e2e-totp-phone (the 1.0 + `invalid_client` bug is gone); send from the 1.0 TOTP account. +- Unified inbox across the logins. + +Not covered on the emulator: series edits, contact photos and +reschedule/undo (covered by the live suites only), and anything iOS. + +## Open + +- iOS, needs a CI run: reader CSP and composer nonce assume injected scripts + ignore page CSP and the editor loads as about:blank; the notification + extension's label check (b352e470, Swift); `target=_blank` link taps; + #81 keyboard. iOS push still lapses after 7 days without opening the app + (the extension can't extend it). +- Blocked on the webmail: org policy (locked settings, session length, + external sender label and recipient confirm, org signature and + disclaimer, template library). The policy route only takes the webmail + session cookie, and the app doesn't keep the paired webmail URL. Also the + hand-off still returns the password in the `bulwarkmobile://` fragment. +- Not done: the 60 P3 rows in the parity file; P3 bugs in Sieve (forward + limit, vacation length, editor losing text); device DST in other-zone + daily expansion keeps a wrong recurrenceId for that one day; a login + switch from a deep link doesn't reset the navigation stack, so a thread + left below can act through the new login. +- New strings are English-only except where the webmail already had + translations. +- The `C:\ns` junction (to the worktree, used for the emulator build) is + still there; removing it was blocked by the permission settings. + +## Bugs that also live in the webmail + +Not fixed there in this run: + +- Inline images of drafts and sent mail on 1.0.0 come only from + `attachments` (components/email/email-viewer.tsx:2518, email-composer.tsx + :905, :1391, :2647). +- Save to Files on 0.16 stores the whole message (save-to-files-dialog.tsx + :79-84); Blob/copy without the blob capability (lib/jmap/client.ts:9985). +- Reschedule after a lost answer: cancel (client.ts:11619) and create + (:11643) can strand or double-send. +- Default alert ids `alert1…` reused across calendars and lists; 1.0 + refuses them (lib/calendar-alerts.ts:206, calendar-management-settings.tsx + :200). +- Sieve parser drops `elsif`/`else` and trailing comments, which breaks the + category-tabs section (lib/sieve/parser.ts:251); generator falls back to + `header :contains` (lib/sieve/generator.ts:126). +- Vacation turned on with a failed filter re-include only logs + (stores/vacation-store.ts:210). +- Feed sync: no END:VCALENDAR check, no mass-deletion guard + (stores/calendar-store.ts:1978); invitation trust lets a failing DKIM + signature beat dmarc=pass (lib/calendar-invitation.ts:219); a HELO SPF + pass counts as aligned (lib/email-headers.ts:64). +- Files trash: purge without a fresh read (stores/file-store.ts:1947), + restore with its folder lands at the root (:1914), no `.Trash` name check. +- Push: no device client id check (lib/web-push.ts:786), cap path destroys + first (:852), refused relay URL shown raw (notification-settings.tsx:173). +- Probably: decoded CSS put back into STYLE (lib/email-sanitization.ts:733), + untested; Lite refresh follows redirects (lib/auth/lite-tokens.ts:533). + +## Questions + +1. #85: the notification Delete action requires unlock (#68), which is why + Android closes the shade. Drop the requirement (Delete then works from the + lock screen, it only moves to Trash) or keep it? +2. Org policy on the phone: add a webmail route that accepts a JMAP bearer + token, and keep the paired webmail URL in the app? +3. Full account security on 1.0 needs a first-party client for the app. Add + an admin-registered `bulwark-mobile` client to the OAuth setup? +4. MIG-02 on native: after a 0.16 to 1.0 migration every token account fails + to refresh and is removed with "Your session has expired". Keep it as + "sign in again" instead? A pasted OAuth access token also drops after + about an hour (no refresh token). +5. A password change from a TOTP account ends its grant on 0.16, so the app + asks to sign in again. Fine? +6. Translate the new English-only strings into the other 26 locales now? +7. Reset the navigation stack when a deep link or notification switches the + login? +8. Small ones: the feed mass-removal guard (keeps events when more than half + of 10+ would go, no override); contact re-import marks same-UID cards as + "update existing", unselected; team templates read-only; Delete + permanently and Restore on stale trash rows refresh silently. +9. The account switcher shows `10.10.10.62` for both servers (port dropped), + so two logins of the same user look identical. Show the port?