diff --git a/PARITY_CHECKLIST.md b/PARITY_CHECKLIST.md new file mode 100644 index 00000000..bb37233a --- /dev/null +++ b/PARITY_CHECKLIST.md @@ -0,0 +1,220 @@ +# Webmail parity checklist + +Audit of **Bulwark Mobile** (`bulwarkmail/native`, v0.1.58 @ `82b97fa`) against +**Bulwark Webmail** (`bulwarkmail/webmail`, v1.9.2 @ `39dcc6b2`), taken on +2026-08-29. The goal is feature *and* bugfix parity: every user-facing webmail +behaviour, and every webmail changelog fix since the native app was started +(1.4.5 → 1.9.2), was checked against the native code. + +The audit was done by reading code on both sides, not by running the app. Every +finding cites `file:line` on both sides so it can be re-verified quickly; a +sample of the 32 P1 findings was additionally spot-checked by hand. + +## How to use this document + +- The detailed findings live in [docs/parity/](docs/parity/), one file per area. + Tick the `- [ ]` boxes there as you fix things; this file is the index. +- Each finding has a **priority** and a **category**: + + | Priority | Meaning | + |---|---| + | `P1` | data loss, wrong send/delete, security, crash, or a core flow that is blocked | + | `P2` | notable feature missing or visibly wrong | + | `P3` | polish, nice-to-have, minor divergence | + + | Category | Meaning | + |---|---| + | `missing` | webmail has it, native does not | + | `partial` | native has a reduced version | + | `bug` | native has it, but it is wrong | + | `bugfix-parity` | a bug the webmail fixed (changelog / issue number cited) that is still live in native | + | `rn-only-bug` | a bug that only exists in native (found while reading) | + +- `WEB` paths are relative to the webmail repo, `RN` paths to this repo. + Issue numbers `#NNN` refer to the **webmail** tracker unless prefixed + "native #NN". +- Each area file ends with **Verified at parity** (do not redo) and + **N/A on mobile** (deliberately out of scope) lists. +- A few findings appear in two areas because they sit on a boundary (e.g. + "forward drops attachments" is in both viewer and composer). Fix once, tick + both. + +## Baseline health of this repo (2026-08-29) + +- [ ] **`npm test` is red on `main`** — `P2` — `rn-only-bug` + - `src/stores/__tests__/auth-store.test.ts` fails to load with + `SyntaxError: Unexpected token 'typeof'`. The chain + `auth-store → email-store → outbox-store → network-store` imports + `@react-native-community/netinfo`, which `src/test-setup.ts` never mocks + (it mocks `react-native`, `expo-secure-store`, `expo-web-browser`, + AsyncStorage only). Broken since `5d47f47` (2026-05-30) when the outbox + import was added; `email-store.test.ts` only passes because it mocks + `../outbox-store` itself. + - Fix hint: add `vi.mock('@react-native-community/netinfo', () => ({ default: { addEventListener: vi.fn(() => () => {}), fetch: vi.fn(async () => ({ isConnected: true, isInternetReachable: true })) } }))` + to `src/test-setup.ts`. Expected afterwards: 31/31 suites (432+ tests). +- [ ] **CI runs neither `tsc` nor `vitest`** — `P2` — `missing` + - `.github/workflows/` only has `release-android.yml` and `release-ios.yml`. + Add a `ci.yml` running `npm ci && npm run typecheck && npm test` on push/PR. +- `npx tsc --noEmit` is clean. +- Uncommitted work in the tree at audit time: ascending/descending mail sort + (native #5) in `src/stores/email-store.ts`, `src/stores/settings-store.ts`, + `src/screens/EmailListScreen.tsx`, `src/stores/__tests__/email-store.test.ts`. + Findings that touch list ordering assume that lands. +- [ ] **README is stale** — `P3` — it says "Filters & rules, S/MIME, plugins, + themes, file storage – UI stubs only". Filters, Files and file sharing are + real (see areas 07); S/MIME, plugins, themes and sidebar apps are still + stubs (area 08). + +## Areas + +| # | Area | File | Items | P1 | P2 | P3 | +|---|---|---|---|---|---|---| +| 01 | Authentication, login, session, multi-account | [docs/parity/01-auth-accounts.md](docs/parity/01-auth-accounts.md) | 30 | 4 | 8 | 18 | +| 02 | Mail list, folders, unified views, search, tags | [docs/parity/02-mail-list-folders.md](docs/parity/02-mail-list-folders.md) | 54 | 1 | 20 | 33 | +| 03 | Email viewer, thread view, rendering, attachments | [docs/parity/03-email-viewer.md](docs/parity/03-email-viewer.md) | 46 | 6 | 13 | 26 | +| 04 | Composer, drafts, sending, identities, templates, scheduled send | [docs/parity/04-composer-send.md](docs/parity/04-composer-send.md) | 51 | 5 | 14 | 32 | +| 05 | Calendar and tasks | [docs/parity/05-calendar.md](docs/parity/05-calendar.md) | 51 | 5 | 20 | 26 | +| 06 | Contacts and address books | [docs/parity/06-contacts.md](docs/parity/06-contacts.md) | 51 | 1 | 18 | 31 | +| 07 | Filters (Sieve), vacation responder, Files | [docs/parity/07-filters-vacation-files.md](docs/parity/07-filters-vacation-files.md) | 33 | 3 | 8 | 22 | +| 08 | Settings, sync, push, i18n, themes, updates, misc UI | [docs/parity/08-settings-push-i18n-ui.md](docs/parity/08-settings-push-i18n-ui.md) | 46 | 0 | 14 | 32 | +| 09 | JMAP client core, live sync, offline, security, S/MIME | [docs/parity/09-jmap-core-sync-security.md](docs/parity/09-jmap-core-sync-security.md) | 56 | 7 | 23 | 26 | +| | **Total** | | **418** | **32** | **138** | **246** | + +Counts are of `- [ ]` items per file (a handful of "verified" notes carry a +priority tag too, so P-sums can differ by one or two from the item count). + +## All P1 findings (fix these first) + +### Data loss / wrong send +- [ ] No draft autosave, no save-on-close, and the OS back gesture bypasses the discard guard — closing a compose loses everything. → [04](docs/parity/04-composer-send.md) +- [ ] Cannot open or edit an existing draft (Drafts folder opens the read-only viewer). → [04](docs/parity/04-composer-send.md) +- [ ] Message is filed into **Sent before `EmailSubmission/set`**; a failed send leaves a never-sent copy in Sent and a retry duplicates it. → [04](docs/parity/04-composer-send.md) +- [ ] `In-Reply-To` / `References` are written with the **JMAP email id** instead of the RFC Message-ID (`messageId`/`references` are never even fetched) — every reply breaks threading in recipients' clients. → [03](docs/parity/03-email-viewer.md), [04](docs/parity/04-composer-send.md) +- [ ] Reply addressing: `Reply-To` ignored, the user's own address kept on reply-all, wrong recipients on self-sent replies (#703, 1.9.0). → [03](docs/parity/03-email-viewer.md) +- [ ] **Forward drops every attachment.** → [03](docs/parity/03-email-viewer.md), [04](docs/parity/04-composer-send.md) +- [ ] Sieve: RN generator has no `attachment` field and emits `header :contains "undefined" ""` for webmail-authored attachment rules — **any save on the phone corrupts the shared server script**. → [07](docs/parity/07-filters-vacation-files.md) +- [ ] Sieve: multi-value conditions (`string[]`, webmail 1.7.3) make the whole script "opaque" on RN; "Reset to visual builder" then wipes all rules. → [07](docs/parity/07-filters-vacation-files.md) +- [ ] `FilterRuleModal` is always mounted and never re-seeds: editing rule B shows and **saves rule A's fields under B's id**. → [07](docs/parity/07-filters-vacation-files.md) +- [ ] Calendar recurrence scope dialog is a stub: "This event" / "This and following" act as "All events" — deleting one occurrence **destroys the series**. → [05](docs/parity/05-calendar.md) +- [ ] Editing an expanded occurrence rewrites the master's `start` to the occurrence date (series shifts, earlier occurrences vanish). → [05](docs/parity/05-calendar.md) +- [ ] All-day events grow by one day on every create-via-toggle and every re-save. → [05](docs/parity/05-calendar.md) +- [ ] Events are created without `timeZone` (floating time). → [05](docs/parity/05-calendar.md) +- [ ] Invitations are never sent: participants use the retired `sendTo`, no organizer participant, no `organizerCalendarAddress` (#500, #731). → [05](docs/parity/05-calendar.md) +- [ ] vCard import writes dates as raw strings instead of RFC 9553 PartialDate (#224) — Stalwart rejects the whole card, so any contact with a birthday is lost on import. → [06](docs/parity/06-contacts.md) + +### Core flow blocked / wrong content shown +- [ ] TOTP / 2FA password login is impossible (402 is shown as a generic failure). → [01](docs/parity/01-auth-accounts.md) +- [ ] Webmail password handoff breaks for TOTP-protected accounts (cross-repo: webmail should hand off `flow=oauth` after a TOTP-upgraded login). → [01](docs/parity/01-auth-accounts.md) +- [ ] A failed "add account" resets the singleton client before connecting, killing the live session until relaunch. → [01](docs/parity/01-auth-accounts.md) +- [ ] Account Security screen is dead against every Stalwart server: it checks `session.capabilities` but Stalwart advertises `urn:stalwart:jmap` only in `accountCapabilities` (verified on 0.16.19) — **native #47**. → [01](docs/parity/01-auth-accounts.md) +- [ ] Thread view: only the newest message of a thread is reachable; older messages cannot be opened unless threading is disabled. → [03](docs/parity/03-email-viewer.md) +- [ ] Pager shows another message's body (or nothing) when the opened id is not in the active folder page — unified inbox, group inboxes, contact activity. → [02](docs/parity/02-mail-list-folders.md), [03](docs/parity/03-email-viewer.md) +- [ ] HTML-only messages are rendered — and quoted in replies — as raw HTML source (**native #46**). `