Three small data/local review fixes from the whole-repo review.
#310 — batch sync header updates in one transaction
MailSyncer refreshed a whole recent window with entities.forEach { updateHeaderContent(...) } = N separate Room transactions (journal write/fsync each) per folder per foreground sync, amplified on the encrypted cache. Added a single @Transaction DAO method MessageDao.updateHeaderContents(List<MessageEntity>) that applies the per-row refreshes in one commit, and routed the sync loop through it. The single-row updateHeaderContent stays (still used by MailBackfiller / MailRepositoryImpl).
The four PagingSource queries ordered by timestampMillis DESC only; timestampMillis isn't unique (bulk mail shares a second), so rows tied at a LIMIT/OFFSET page boundary could duplicate or skip. Appended the id primary key (ORDER BY timestampMillis DESC, id) for a total order. id is already in the projection, so no new index.
No schema re-export / version bump: the exported Room identityHash is derived from table/index/view structure, not @Query SQL. Confirmed — KSP ran clean and git status shows no change under app/schemas/.
MigrationTest reflectively discovers and replay-tests every Migration, but nothing asserted DatabaseModule actually registers them. With no destructive fallback, a migration authored + schema-committed but forgotten in addMigrations passes every test yet crash-loops all upgrading users at DB open. Exposed the registered list as DatabaseModule.ALL_MIGRATIONS (spread into addMigrations) and added a MigrationTest assertion that it equals the reflectively-discovered set.
New MessageDaoTest: batch-update refresh, and the id tiebreaker across all four pagers (browse + search).
New MigrationTest.databaseModuleRegistersEveryDeclaredMigration.
Wired updateHeaderContents into MailSyncConcurrencyTest's fake DAO.
Green locally: :app:testDebugUnitTest + :app:compileDebugAndroidTestKotlin + :app:ktlintCheck + :app:detekt, and instrumented MessageDaoTest + MigrationTest (31 tests) on a cold-booted emulator.
Three small data/local review fixes from the whole-repo review.
## #310 — batch sync header updates in one transaction
`MailSyncer` refreshed a whole recent window with `entities.forEach { updateHeaderContent(...) }` = N separate Room transactions (journal write/fsync each) per folder per foreground sync, amplified on the encrypted cache. Added a single `@Transaction` DAO method `MessageDao.updateHeaderContents(List<MessageEntity>)` that applies the per-row refreshes in one commit, and routed the sync loop through it. The single-row `updateHeaderContent` stays (still used by `MailBackfiller` / `MailRepositoryImpl`).
Closes #310
## #311 — paging ORDER BY tiebreaker
The four `PagingSource` queries ordered by `timestampMillis DESC` only; `timestampMillis` isn't unique (bulk mail shares a second), so rows tied at a LIMIT/OFFSET page boundary could duplicate or skip. Appended the `id` primary key (`ORDER BY timestampMillis DESC, id`) for a total order. `id` is already in the projection, so no new index.
No schema re-export / version bump: the exported Room `identityHash` is derived from table/index/view structure, not `@Query` SQL. Confirmed — KSP ran clean and `git status` shows no change under `app/schemas/`.
Closes #311
## #312 — migration-registration safety net
`MigrationTest` reflectively discovers and replay-tests every `Migration`, but nothing asserted `DatabaseModule` actually *registers* them. With no destructive fallback, a migration authored + schema-committed but forgotten in `addMigrations` passes every test yet crash-loops all upgrading users at DB open. Exposed the registered list as `DatabaseModule.ALL_MIGRATIONS` (spread into `addMigrations`) and added a `MigrationTest` assertion that it equals the reflectively-discovered set.
Closes #312
## Tests / validation
- New `MessageDaoTest`: batch-update refresh, and the `id` tiebreaker across all four pagers (browse + search).
- New `MigrationTest.databaseModuleRegistersEveryDeclaredMigration`.
- Wired `updateHeaderContents` into `MailSyncConcurrencyTest`'s fake DAO.
- Green locally: `:app:testDebugUnitTest` + `:app:compileDebugAndroidTestKotlin` + `:app:ktlintCheck` + `:app:detekt`, and instrumented `MessageDaoTest` + `MigrationTest` (31 tests) on a cold-booted emulator.
🤖 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.
Three small data/local review fixes from the whole-repo review.
#310 — batch sync header updates in one transaction
MailSyncerrefreshed a whole recent window withentities.forEach { updateHeaderContent(...) }= N separate Room transactions (journal write/fsync each) per folder per foreground sync, amplified on the encrypted cache. Added a single@TransactionDAO methodMessageDao.updateHeaderContents(List<MessageEntity>)that applies the per-row refreshes in one commit, and routed the sync loop through it. The single-rowupdateHeaderContentstays (still used byMailBackfiller/MailRepositoryImpl).Closes #310
#311 — paging ORDER BY tiebreaker
The four
PagingSourcequeries ordered bytimestampMillis DESConly;timestampMillisisn't unique (bulk mail shares a second), so rows tied at a LIMIT/OFFSET page boundary could duplicate or skip. Appended theidprimary key (ORDER BY timestampMillis DESC, id) for a total order.idis already in the projection, so no new index.No schema re-export / version bump: the exported Room
identityHashis derived from table/index/view structure, not@QuerySQL. Confirmed — KSP ran clean andgit statusshows no change underapp/schemas/.Closes #311
#312 — migration-registration safety net
MigrationTestreflectively discovers and replay-tests everyMigration, but nothing assertedDatabaseModuleactually registers them. With no destructive fallback, a migration authored + schema-committed but forgotten inaddMigrationspasses every test yet crash-loops all upgrading users at DB open. Exposed the registered list asDatabaseModule.ALL_MIGRATIONS(spread intoaddMigrations) and added aMigrationTestassertion that it equals the reflectively-discovered set.Closes #312
Tests / validation
MessageDaoTest: batch-update refresh, and theidtiebreaker across all four pagers (browse + search).MigrationTest.databaseModuleRegistersEveryDeclaredMigration.updateHeaderContentsintoMailSyncConcurrencyTest's fake DAO.:app:testDebugUnitTest+:app:compileDebugAndroidTestKotlin+:app:ktlintCheck+:app:detekt, and instrumentedMessageDaoTest+MigrationTest(31 tests) on a cold-booted emulator.🤖 Generated with Claude Code