fix!: IMAP IDLE behavior (#433)

Drew Smirnoff created

Change summary

fetcher/idle.go | 31 +++++++++++++++++++++++++++++--
main.go         |  7 +++++++
2 files changed, 36 insertions(+), 2 deletions(-)

Detailed changes

fetcher/idle.go 🔗

@@ -47,8 +47,8 @@ func (w *IdleWatcher) Watch(account *config.Account, folder string) {
 	// Stop existing watcher for this account if any
 	if existing, ok := w.watchers[account.ID]; ok {
 		close(existing.stop)
-		<-existing.done
 		delete(w.watchers, account.ID)
+		// Let old connection tear down in the background
 	}
 
 	a := &accountIdle{
@@ -62,6 +62,18 @@ func (w *IdleWatcher) Watch(account *config.Account, folder string) {
 	go a.run()
 }
 
+// Stop stops the IDLE watcher for a specific account.
+func (w *IdleWatcher) Stop(accountID string) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	if a, ok := w.watchers[accountID]; ok {
+		close(a.stop)
+		delete(w.watchers, accountID)
+		// Let old connection tear down in the background
+	}
+}
+
 // StopAll stops all IDLE watchers.
 func (w *IdleWatcher) StopAll() {
 	w.mu.Lock()
@@ -69,11 +81,26 @@ func (w *IdleWatcher) StopAll() {
 
 	for id, a := range w.watchers {
 		close(a.stop)
-		<-a.done
 		delete(w.watchers, id)
 	}
 }
 
+// StopAllAndWait stops all IDLE watchers and waits for them to finish.
+func (w *IdleWatcher) StopAllAndWait() {
+	w.mu.Lock()
+	var pending []chan struct{}
+	for id, a := range w.watchers {
+		close(a.stop)
+		pending = append(pending, a.done)
+		delete(w.watchers, id)
+	}
+	w.mu.Unlock()
+
+	for _, done := range pending {
+		<-done
+	}
+}
+
 func (a *accountIdle) run() {
 	defer close(a.done)
 

main.go 🔗

@@ -15,6 +15,7 @@ import (
 	"path/filepath"
 	"regexp"
 	"runtime"
+	"slices"
 	"strings"
 	"sync"
 	"time"
@@ -416,6 +417,12 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		}
 		// Update IDLE watchers to monitor the new folder
 		for i := range m.config.Accounts {
+			// Only start IDLE for accounts that actually have this folder
+			folders := config.GetCachedFolders(m.config.Accounts[i].ID)
+			if !slices.Contains(folders, msg.FolderName) {
+				m.idleWatcher.Stop(m.config.Accounts[i].ID)
+				continue
+			}
 			m.idleWatcher.Watch(&m.config.Accounts[i], msg.FolderName)
 		}
 		if m.plugins != nil {