On a Gmail account the drawer showed Drafts, Archive, and Spam more than once. The cause was a display-layer collapse: folderDisplayLabel rendered every standard-role folder with a generic friendly name ("Drafts", "Archive", …) and discarded the distinguishing server name. When an account has both the provider's built-in special folder and a same-named user folder, both collapsed to one entry:
[Gmail]/Drafts (\Drafts) and a label Drafts → both "Drafts"
[Gmail]/All Mail (→ ARCHIVE) and a label Archive → both "Archive"
[Gmail]/Spam (\Junk) and a label Spam → both "Spam"
These are genuinely distinct folders, so the fix relabels rather than hides.
Fix
A provider-agnostic de-duplication rule: when 2+ folders would render the same label,
the provider's built-in special folder (identified by IMAP SPECIAL-USE flags, RFC 6154) gets the provider name appended → Archive - Gmail;
a nested user folder gets its parent location → Reports (Work);
a top-level user folder keeps its plain name.
It only triggers on a real collision, so stock accounts are unchanged. The provider suffix is a recognized brand (Gmail/Yahoo Mail/iCloud Mail/Outlook), else the email domain.
Implementation
Room v11 → v12: persist a specialUse flag on the folders cache (ALTER TABLE … ADD COLUMN specialUse INTEGER NOT NULL DEFAULT 0, matching @ColumnInfo(defaultValue = "0"); schema 12.json committed). Existing rows migrate to false and are backfilled on the next folder refresh (which the drawer already triggers on open).
FolderRole.isServerSpecial(attributes) classifies the SPECIAL-USE set; Folder.specialUse carries it; MailProvider.forImapHost(host) resolves the brand.
New Compose-free, unit-tested FolderLabels.kt (providerLabel + pure resolveDrawerLabels), wired into FolderDrawer.
Tests
New JVM FolderLabelsTest — all three reported cases, nested-folder parents, no-op path, provider-label resolution, and a uniqueness safety net.
FolderRoleTest.isServerSpecial cases; a Gmail duplicate case in FolderDrawerTest; specialUse round-trip in LibreMailDatabaseTest.
## Problem
On a Gmail account the drawer showed **Drafts, Archive, and Spam more than once**. The cause was a display-layer collapse: `folderDisplayLabel` rendered every standard-role folder with a generic friendly name ("Drafts", "Archive", …) and discarded the distinguishing server name. When an account has both the provider's built-in special folder **and** a same-named user folder, both collapsed to one entry:
- `[Gmail]/Drafts` (`\Drafts`) **and** a label `Drafts` → both "Drafts"
- `[Gmail]/All Mail` (→ `ARCHIVE`) **and** a label `Archive` → both "Archive"
- `[Gmail]/Spam` (`\Junk`) **and** a label `Spam` → both "Spam"
These are genuinely distinct folders, so the fix relabels rather than hides.
## Fix
A provider-agnostic de-duplication rule: when 2+ folders would render the same label,
- the provider's **built-in special folder** (identified by IMAP SPECIAL-USE flags, RFC 6154) gets the provider name appended → `Archive - Gmail`;
- a **nested** user folder gets its parent location → `Reports (Work)`;
- a **top-level** user folder keeps its plain name.
It only triggers on a real collision, so stock accounts are unchanged. The provider suffix is a recognized brand (`Gmail`/`Yahoo Mail`/`iCloud Mail`/`Outlook`), else the email domain.
## Implementation
- **Room v11 → v12:** persist a `specialUse` flag on the `folders` cache (`ALTER TABLE … ADD COLUMN specialUse INTEGER NOT NULL DEFAULT 0`, matching `@ColumnInfo(defaultValue = "0")`; schema `12.json` committed). Existing rows migrate to `false` and are backfilled on the next folder refresh (which the drawer already triggers on open).
- `FolderRole.isServerSpecial(attributes)` classifies the SPECIAL-USE set; `Folder.specialUse` carries it; `MailProvider.forImapHost(host)` resolves the brand.
- New Compose-free, unit-tested `FolderLabels.kt` (`providerLabel` + pure `resolveDrawerLabels`), wired into `FolderDrawer`.
## Tests
- New JVM `FolderLabelsTest` — all three reported cases, nested-folder parents, no-op path, provider-label resolution, and a uniqueness safety net.
- `FolderRoleTest.isServerSpecial` cases; a Gmail duplicate case in `FolderDrawerTest`; `specialUse` round-trip in `LibreMailDatabaseTest`.
- Locally green: `assembleDebug` + `testDebugUnitTest` + `lintDebug` + `compileDebugAndroidTestKotlin`.
🤖 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.
Problem
On a Gmail account the drawer showed Drafts, Archive, and Spam more than once. The cause was a display-layer collapse:
folderDisplayLabelrendered every standard-role folder with a generic friendly name ("Drafts", "Archive", …) and discarded the distinguishing server name. When an account has both the provider's built-in special folder and a same-named user folder, both collapsed to one entry:[Gmail]/Drafts(\Drafts) and a labelDrafts→ both "Drafts"[Gmail]/All Mail(→ARCHIVE) and a labelArchive→ both "Archive"[Gmail]/Spam(\Junk) and a labelSpam→ both "Spam"These are genuinely distinct folders, so the fix relabels rather than hides.
Fix
A provider-agnostic de-duplication rule: when 2+ folders would render the same label,
Archive - Gmail;Reports (Work);It only triggers on a real collision, so stock accounts are unchanged. The provider suffix is a recognized brand (
Gmail/Yahoo Mail/iCloud Mail/Outlook), else the email domain.Implementation
specialUseflag on thefolderscache (ALTER TABLE … ADD COLUMN specialUse INTEGER NOT NULL DEFAULT 0, matching@ColumnInfo(defaultValue = "0"); schema12.jsoncommitted). Existing rows migrate tofalseand are backfilled on the next folder refresh (which the drawer already triggers on open).FolderRole.isServerSpecial(attributes)classifies the SPECIAL-USE set;Folder.specialUsecarries it;MailProvider.forImapHost(host)resolves the brand.FolderLabels.kt(providerLabel+ pureresolveDrawerLabels), wired intoFolderDrawer.Tests
FolderLabelsTest— all three reported cases, nested-folder parents, no-op path, provider-label resolution, and a uniqueness safety net.FolderRoleTest.isServerSpecialcases; a Gmail duplicate case inFolderDrawerTest;specialUseround-trip inLibreMailDatabaseTest.assembleDebug+testDebugUnitTest+lintDebug+compileDebugAndroidTestKotlin.🤖 Generated with Claude Code