diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b6d4fa..96f3380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +# 2026-10-06 + +- **The Drafts tab showed the Inbox** — "Drafts" is in `defaultTabOrder`, so it + renders as a regular tab, but `activeFolder()`'s label→mailbox switch had no + `"Drafts"` case: every unmatched label fell through to `default` and fetched + the Inbox mailbox, so the tab displayed Inbox mail no matter what the webmail + showed in the real Drafts folder. Only the tab was affected — `gd` (off-tab + Drafts view) maps through a separate switch and was always correct. Added the + missing case (`internal/ui/model.go`, `activeFolder`). Side effect: `S` / + `:screen` now correctly refuse on the Drafts tab (`onPlainInboxTab`), where + they previously misidentified it as the Inbox. Test: + `TestActiveFolderDraftsTab`. + # 2026-10-05 - **The folder window is the newest mail by arrival date, not the highest UIDs (#34)** — `FetchHeaders` picked its `inbox_count` window as the highest `n` UIDs. IMAP MOVE/COPY give a message a fresh, highest UID in the destination mailbox (RFC 9051 per-mailbox UIDs), so every mail bulk-moved *into* a folder — `I` approve on ToScreen, `:reset-toscreen`, `U` undo — landed above all real recent mail, filled the window, and pushed the recent mail below the cutoff where no display sort (`,m`) could bring it back. Only folders with more than `inbox_count` messages were affected, which is why a HEY-style Inbox of a dozen rows never showed it while a 12'000-mail legacy Inbox after its first screening did (reproduced on Hostpoint/Dovecot, so not an Infomaniak quirk; the demo's `moves.log` shows `ToScreen uid=273 → INBOX destUID=1397`). `FetchHeaders`, `FetchLatest` (`:everything`) and the per-folder search cap (`searchFolderCap`, was a literal 100) now narrow the window with one cheap `UID FETCH (UID INTERNALDATE)` and keep the `n` most recently received (`newestUIDsByInternalDate`, `internal/imap/client.go`); INTERNALDATE survives MOVE/COPY on every server tested, and the SORT extension is not used because Infomaniak does not offer it. Folders at or below `inbox_count`, and `inbox_count = 0`, take the old single-round-trip path unchanged, so a daily Inbox sees no new traffic. Verified on the live Hostpoint demo: a 10-row window returned April mail before, October mail after. Tests: `TestMem_FetchHeaders_WindowIsNewestByInternalDateNotUID`, `TestMem_FetchHeaders_FolderWithinLimitUnchanged`, `TestMem_FetchLatest_WindowIsNewestByInternalDateNotUID`, `TestMem_SearchFolder_CapIsNewestByInternalDateNotUID`. diff --git a/internal/ui/model.go b/internal/ui/model.go index 61bc6c1..de41a21 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -1134,6 +1134,8 @@ func (m Model) activeFolder() string { return m.cfg.Folders.Sent case "Trash": return m.cfg.Folders.Trash + case "Drafts": + return m.cfg.Folders.Drafts case "Archive": return m.cfg.Folders.Archive case "Waiting": diff --git a/internal/ui/model_test.go b/internal/ui/model_test.go index 45fc3a8..0563e57 100644 --- a/internal/ui/model_test.go +++ b/internal/ui/model_test.go @@ -457,6 +457,25 @@ func TestActiveFolderUsesOffTabFolder(t *testing.T) { } } +// TestActiveFolderDraftsTab — the Drafts tab (in defaultTabOrder) must fetch +// the Drafts mailbox; the label switch in activeFolder() had no Drafts case, +// so the tab fell through to default and showed the Inbox instead. +func TestActiveFolderDraftsTab(t *testing.T) { + m := Model{ + cfg: &config.Config{ + Folders: config.FoldersConfig{ + Inbox: "INBOX", + Drafts: "Drafts", + }, + }, + folders: []string{"Inbox", "Drafts"}, + activeFolderI: 1, + } + if got := m.activeFolder(); got != "Drafts" { + t.Fatalf("activeFolder() on the Drafts tab = %q, want %q (must not fall back to the Inbox)", got, "Drafts") + } +} + func TestUpdateInboxEscClearsCommittedFilter(t *testing.T) { m := Model{ filterText: "invoice",