Origin: code review of PR #54 (drawer folder de-duplication). Highest-severity finding of that review: user mail is routed to the wrong folder with no feedback. The behavior predates #54, but #54 added exactly the signal needed to fix it.
Problem
resolveRoleFolder().pick() (MailRepositoryImpl.kt:261) picks the destination for archive() / reportSpam() / trash() as the first selectable folder whose role matches, in server LIST order (FolderDao.getForAccountOnce is ORDER BY sortOrder ASC; sortOrder is the LIST index). A provider's built-in folder and a same-named user folder can hold the same role — [Gmail]/Spam gets SPAM via \Junk, a user label "Spam" gets SPAM via roleFromDisplayName — so the winner is whichever the server happened to list first. PR #54 persists specialUse (RFC 6154) on the very rows pick() scans, but only consumes it for drawer labels.
Failure scenario
Gmail account with a user label "Spam" alongside [Gmail]/Spam: tapping Report spam moves messages to whichever folder the server LISTed first. If the user label wins, mail never reaches Gmail's junk mailbox — no spam training, no 30-day auto-purge. The confirm dialog never names the destination ("Move N message(s) to the Spam folder?") and local rows are deleted optimistically, so the misfile is silent. The same order-dependence affects archive ([Gmail]/All Mail vs user "Archive") and trash (provider retention/purge skipped).
Suggested fix
One line in pick() — prefer the special-use match:
Plus a MailRepositoryImplTest case with both same-role folders present in each LIST order.
Origin: code review of PR #54 (drawer folder de-duplication). Highest-severity finding of that review: user mail is routed to the wrong folder with no feedback. The behavior predates #54, but #54 added exactly the signal needed to fix it.
## Problem
`resolveRoleFolder().pick()` (`MailRepositoryImpl.kt:261`) picks the destination for `archive()` / `reportSpam()` / `trash()` as the **first** selectable folder whose role matches, in server LIST order (`FolderDao.getForAccountOnce` is `ORDER BY sortOrder ASC`; `sortOrder` is the LIST index). A provider's built-in folder and a same-named user folder can hold the same role — `[Gmail]/Spam` gets SPAM via `\Junk`, a user label "Spam" gets SPAM via `roleFromDisplayName` — so the winner is whichever the server happened to list first. PR #54 persists `specialUse` (RFC 6154) on the very rows `pick()` scans, but only consumes it for drawer labels.
## Failure scenario
Gmail account with a user label "Spam" alongside `[Gmail]/Spam`: tapping **Report spam** moves messages to whichever folder the server LISTed first. If the user label wins, mail never reaches Gmail's junk mailbox — no spam training, no 30-day auto-purge. The confirm dialog never names the destination ("Move N message(s) to the Spam folder?") and local rows are deleted optimistically, so the misfile is silent. The same order-dependence affects archive (`[Gmail]/All Mail` vs user "Archive") and trash (provider retention/purge skipped).
## Suggested fix
One line in `pick()` — prefer the special-use match:
```kotlin
folders.filter { it.role == role.name && it.selectable }.maxByOrNull { it.specialUse }?.fullName
```
Plus a `MailRepositoryImplTest` case with both same-role folders present in each LIST order.
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.
Origin: code review of PR #54 (drawer folder de-duplication). Highest-severity finding of that review: user mail is routed to the wrong folder with no feedback. The behavior predates #54, but #54 added exactly the signal needed to fix it.
Problem
resolveRoleFolder().pick()(MailRepositoryImpl.kt:261) picks the destination forarchive()/reportSpam()/trash()as the first selectable folder whose role matches, in server LIST order (FolderDao.getForAccountOnceisORDER BY sortOrder ASC;sortOrderis the LIST index). A provider's built-in folder and a same-named user folder can hold the same role —[Gmail]/Spamgets SPAM via\Junk, a user label "Spam" gets SPAM viaroleFromDisplayName— so the winner is whichever the server happened to list first. PR #54 persistsspecialUse(RFC 6154) on the very rowspick()scans, but only consumes it for drawer labels.Failure scenario
Gmail account with a user label "Spam" alongside
[Gmail]/Spam: tapping Report spam moves messages to whichever folder the server LISTed first. If the user label wins, mail never reaches Gmail's junk mailbox — no spam training, no 30-day auto-purge. The confirm dialog never names the destination ("Move N message(s) to the Spam folder?") and local rows are deleted optimistically, so the misfile is silent. The same order-dependence affects archive ([Gmail]/All Mailvs user "Archive") and trash (provider retention/purge skipped).Suggested fix
One line in
pick()— prefer the special-use match:Plus a
MailRepositoryImplTestcase with both same-role folders present in each LIST order.