Two client-side memory items from the performance review.
Message bodies are held for the 40 most recently wanted messages.
Before: every message opened kept its full copy for as long as the tab was open: HTML and text bodies up to 2 MB each, parsed headers, and the attachment list.
What gets tracked:getEmails(ids, true) records which messages have been wanted in full, most recent last.
Releasing old ones: past BODIES_KEPT (40), releaseBodies takes the least recent messages back to their LIST_PROPS and removes them from fullIds. Messages in the open conversation are skipped.
Why this is safe: everything that needs a body fetches it through getEmails(…, true) (the thread view, reply and forward, appointments), and the reading pane only renders messages in fullIds. A released message is simply fetched in full again when it's next opened.
Account switch: the list of held bodies is cleared.
Exported files are released. A new lib/download.tsdownloadFile() creates the object URL, clicks the link and releases the URL straight away, as the calendar export already did. The contact-book export, the single-contact vCard and the settings export now use it; none of the three released their URL before. The calendar export uses the helper too.
Related issues
None.
Translations
Adds none. npm run i18n:check reports the same counts as main.
Testing
New body-eviction.test.ts covers four cases:
past the limit, the oldest messages lose their bodies but keep their list fields;
reopening a message counts it as recent again;
the open conversation is never released;
a released message is fetched in full again.
All 4 fail against main.
npm test -w web (1,335 tests), npm run typecheck, npm run build -w web and npm run i18n:check are clean.
Checked in Chrome against npm run dev:mock:
Opened a three-message conversation, then moved to another one.
Fetched 50 other messages in full through the app's store. That left 40 bodies held, and the first conversation's three messages released.
Reopened the first conversation from the list. All three messages were fetched in full again, and the latest one rendered its HTML body.
## Summary
Two client-side memory items from the performance review.
**Message bodies are held for the 40 most recently wanted messages.**
- **Before:** every message opened kept its full copy for as long as the tab was open: HTML and text bodies up to 2 MB each, parsed headers, and the attachment list.
- **What gets tracked:** `getEmails(ids, true)` records which messages have been wanted in full, most recent last.
- **Releasing old ones:** past `BODIES_KEPT` (40), `releaseBodies` takes the least recent messages back to their `LIST_PROPS` and removes them from `fullIds`. Messages in the open conversation are skipped.
- **Why this is safe:** everything that needs a body fetches it through `getEmails(…, true)` (the thread view, reply and forward, appointments), and the reading pane only renders messages in `fullIds`. A released message is simply fetched in full again when it's next opened.
- **Account switch:** the list of held bodies is cleared.
**Exported files are released.** A new `lib/download.ts` `downloadFile()` creates the object URL, clicks the link and releases the URL straight away, as the calendar export already did. The contact-book export, the single-contact vCard and the settings export now use it; none of the three released their URL before. The calendar export uses the helper too.
## Related issues
None.
## Translations
Adds none. `npm run i18n:check` reports the same counts as `main`.
## Testing
- New `body-eviction.test.ts` covers four cases:
- past the limit, the oldest messages lose their bodies but keep their list fields;
- reopening a message counts it as recent again;
- the open conversation is never released;
- a released message is fetched in full again.
**All 4 fail against `main`.**
- `npm test -w web` (1,335 tests), `npm run typecheck`, `npm run build -w web` and `npm run i18n:check` are clean.
- Checked in Chrome against `npm run dev:mock`:
1. Opened a three-message conversation, then moved to another one.
2. Fetched 50 other messages in full through the app's store. That left 40 bodies held, and the first conversation's three messages released.
3. Reopened the first conversation from the list. All three messages were fetched in full again, and the latest one rendered its HTML body.
**Merged** 2026-09-16 as coffey-labs/ihasmail@aa9bf1b9b25e
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Two client-side memory items from the performance review.
Message bodies are held for the 40 most recently wanted messages.
getEmails(ids, true)records which messages have been wanted in full, most recent last.BODIES_KEPT(40),releaseBodiestakes the least recent messages back to theirLIST_PROPSand removes them fromfullIds. Messages in the open conversation are skipped.getEmails(…, true)(the thread view, reply and forward, appointments), and the reading pane only renders messages infullIds. A released message is simply fetched in full again when it's next opened.Exported files are released. A new
lib/download.tsdownloadFile()creates the object URL, clicks the link and releases the URL straight away, as the calendar export already did. The contact-book export, the single-contact vCard and the settings export now use it; none of the three released their URL before. The calendar export uses the helper too.Related issues
None.
Translations
Adds none.
npm run i18n:checkreports the same counts asmain.Testing
New
body-eviction.test.tscovers four cases:All 4 fail against
main.npm test -w web(1,335 tests),npm run typecheck,npm run build -w webandnpm run i18n:checkare clean.Checked in Chrome against
npm run dev:mock:Merged 2026-09-16 as coffey-labs/ihasmail@aa9bf1b9b2
Rebuilt from: git history, session transcript.