Validates and fixes the mailbox-list slowness in #86. The list observed the wholemessages
table (observeSummaries, no WHERE/LIMIT), mapped every cached row to a domain Message, and
filtered down to the visible account+folder in MailboxViewModel — so its cost scaled with the
total cache and re-ran on every write to messages (IDLE delivery, a read/star toggle, a
backfill page, any folder sync).
The fix pushes the account/folder filter into SQL and flatMapLatestes the ViewModel over the
selected account+folder; the only remaining client-side pass separates the normal list from an
active search over the small folder-scoped set.
Profiling first (the ticket asked for validation before a fix)
Measured on an emulator (API 29) against 1k / 5k / 20k-row caches with a fixed 150-row visible page.
Full report: docs/perf/issue-86-profiling.md.
total rows
CURRENT whole-table + filter
SCOPED acct+folder
speedup
1 000
~5 ms
~1.5 ms
~3×
5 000
~25 ms
~1.5 ms
~17×
20 000
~125 ms
~1.5 ms
~80×
Theory confirmed: the current path is O(total cache); the scoped query is flat.
No index, no migration.EXPLAIN QUERY PLAN shows the account-scoped query is already served
by the existing(accountId, folder, uid) index (added in v13). The ticket's proposed (accountId, folder, inInbox, timestampMillis) composite index moves the timing only within noise
(80× → 87×) and SQLite's planner doesn't even prefer it when present. So this PR adds no schema
change — the cache DB stays at v14, and there is no coordination needed with #118's v15.
Second contributor: the unified "All inboxes" view is folder-only (no leading index) so it
stays an O(N) scan — still 5.6× better here (materializes only INBOX rows), but its real fix is paging (± a (folder, timestampMillis) index), deferred with a note. IMAP round-trip latency
on folder open is a separate, unmeasured network concern.
Changes
MessageDao: observeFolderSummaries(account, folder) + observeUnifiedFolderSummaries(folder)
(SQL-scoped, inInbox-agnostic so one query serves both list and search).
Throwaway benchmark probe removed after measuring (its methodology + numbers live in the doc); it
wasn't kept because it would burden the 8-device CI E2E matrix.
Testing
Fast gate green: assembleDebug + testDebugUnitTest + lintDebug + ktlintCheck + detekt
and compileDebugAndroidTestKotlin.
MailboxScreenTest (11 tests) run on the emulator to verify the refactored flow renders end-to-end.
## Summary
Validates and fixes the mailbox-list slowness in #86. The list observed the **whole** `messages`
table (`observeSummaries`, no `WHERE`/`LIMIT`), mapped every cached row to a domain `Message`, and
filtered down to the visible account+folder in `MailboxViewModel` — so its cost scaled with the
total cache and re-ran on **every** write to `messages` (IDLE delivery, a read/star toggle, a
backfill page, any folder sync).
The fix pushes the account/folder filter into SQL and `flatMapLatest`es the ViewModel over the
selected account+folder; the only remaining client-side pass separates the normal list from an
active search over the small folder-scoped set.
## Profiling first (the ticket asked for validation before a fix)
Measured on an emulator (API 29) against 1k / 5k / 20k-row caches with a fixed 150-row visible page.
Full report: [`docs/perf/issue-86-profiling.md`](docs/perf/issue-86-profiling.md).
| total rows | CURRENT whole-table + filter | SCOPED acct+folder | speedup |
|-----------:|-----------------------------:|-------------------:|--------:|
| 1 000 | ~5 ms | ~1.5 ms | ~3× |
| 5 000 | ~25 ms | ~1.5 ms | ~17× |
| 20 000 | ~125 ms | ~1.5 ms | **~80×** |
- **Theory confirmed:** the current path is O(total cache); the scoped query is flat.
- **No index, no migration.** `EXPLAIN QUERY PLAN` shows the account-scoped query is already served
by the **existing** `(accountId, folder, uid)` index (added in v13). The ticket's proposed
`(accountId, folder, inInbox, timestampMillis)` composite index moves the timing only within noise
(80× → 87×) and SQLite's planner doesn't even prefer it when present. So this PR adds **no schema
change** — the cache DB stays at **v14**, and there is **no coordination needed with #118's v15**.
- **Second contributor:** the unified "All inboxes" view is folder-only (no leading index) so it
stays an O(N) scan — still 5.6× better here (materializes only INBOX rows), but its real fix is
**paging** (± a `(folder, timestampMillis)` index), deferred with a note. IMAP round-trip latency
on folder open is a separate, unmeasured network concern.
## Changes
- `MessageDao`: `observeFolderSummaries(account, folder)` + `observeUnifiedFolderSummaries(folder)`
(SQL-scoped, `inInbox`-agnostic so one query serves both list and search).
- `MailRepository`: `observeMessages()` → `observeFolderMessages` / `observeUnifiedFolderMessages`.
- `MailboxViewModel.messages`: `flatMapLatest` over the scoped flow; client filter reduced to
`inInbox` vs `matchesSearch` over the folder-scoped set.
- Tests updated (`MailboxViewModelTest`, `MailRepositoryImplTest`, `FakeMailRepository`).
- Throwaway benchmark probe removed after measuring (its methodology + numbers live in the doc); it
wasn't kept because it would burden the 8-device CI E2E matrix.
## Testing
- Fast gate green: `assembleDebug` + `testDebugUnitTest` + `lintDebug` + `ktlintCheck` + `detekt`
and `compileDebugAndroidTestKotlin`.
- `MailboxScreenTest` (11 tests) run on the emulator to verify the refactored flow renders end-to-end.
Closes #86
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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
Validates and fixes the mailbox-list slowness in #86. The list observed the whole
messagestable (
observeSummaries, noWHERE/LIMIT), mapped every cached row to a domainMessage, andfiltered down to the visible account+folder in
MailboxViewModel— so its cost scaled with thetotal cache and re-ran on every write to
messages(IDLE delivery, a read/star toggle, abackfill page, any folder sync).
The fix pushes the account/folder filter into SQL and
flatMapLatestes the ViewModel over theselected account+folder; the only remaining client-side pass separates the normal list from an
active search over the small folder-scoped set.
Profiling first (the ticket asked for validation before a fix)
Measured on an emulator (API 29) against 1k / 5k / 20k-row caches with a fixed 150-row visible page.
Full report:
docs/perf/issue-86-profiling.md.EXPLAIN QUERY PLANshows the account-scoped query is already servedby the existing
(accountId, folder, uid)index (added in v13). The ticket's proposed(accountId, folder, inInbox, timestampMillis)composite index moves the timing only within noise(80× → 87×) and SQLite's planner doesn't even prefer it when present. So this PR adds no schema
change — the cache DB stays at v14, and there is no coordination needed with #118's v15.
stays an O(N) scan — still 5.6× better here (materializes only INBOX rows), but its real fix is
paging (± a
(folder, timestampMillis)index), deferred with a note. IMAP round-trip latencyon folder open is a separate, unmeasured network concern.
Changes
MessageDao:observeFolderSummaries(account, folder)+observeUnifiedFolderSummaries(folder)(SQL-scoped,
inInbox-agnostic so one query serves both list and search).MailRepository:observeMessages()→observeFolderMessages/observeUnifiedFolderMessages.MailboxViewModel.messages:flatMapLatestover the scoped flow; client filter reduced toinInboxvsmatchesSearchover the folder-scoped set.MailboxViewModelTest,MailRepositoryImplTest,FakeMailRepository).wasn't kept because it would burden the 8-device CI E2E matrix.
Testing
assembleDebug+testDebugUnitTest+lintDebug+ktlintCheck+detektand
compileDebugAndroidTestKotlin.MailboxScreenTest(11 tests) run on the emulator to verify the refactored flow renders end-to-end.Closes #86
🤖 Generated with Claude Code