diff --git a/main.go b/main.go index f3a4c30a9f17de4d1e8a4c5e6f6ace65e7fc1ed4..9683a7669659cc003cf5b39ce67ce25d29dc6880 100644 --- a/main.go +++ b/main.go @@ -549,6 +549,44 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } go config.SaveAccountFolders(accID, names) } + // Per-account fetch errors (e.g. broken IMAP login, unreachable + // server) are non-fatal: other accounts' folders are still shown. + // Surface them as a transient overlay so the user knows why an + // account's folders are missing instead of silently dropping them. + // Reuses the PluginNotifyMsg pattern (save current view, show + // status with a tea.Tick that fires RestoreViewMsg). + if len(msg.Errors) > 0 { + lookup := map[string]string{} + if m.config != nil { + for _, acc := range m.config.Accounts { + name := acc.Email + if name == "" { + name = acc.Name + } + if name == "" { + name = acc.ID + } + lookup[acc.ID] = name + } + } + parts := make([]string, 0, len(msg.Errors)) + for accID, err := range msg.Errors { + name := lookup[accID] + if name == "" { + name = accID + } + parts = append(parts, fmt.Sprintf("%s: %v", name, err)) + } + sort.Strings(parts) + m.previousModel = m.current + m.current = tui.NewStatus(fmt.Sprintf( + "Folder fetch failed for %d account(s): %s", + len(parts), strings.Join(parts, "; "), + )) + return m, tea.Tick(4*time.Second, func(t time.Time) tea.Msg { + return tui.RestoreViewMsg{} + }) + } return m, nil case tui.SwitchFolderMsg: @@ -2707,6 +2745,7 @@ func fetchFoldersCmd(cfg *config.Config) tea.Cmd { return nil } foldersByAccount := make(map[string][]fetcher.Folder) + errsByAccount := make(map[string]error) seen := make(map[string]fetcher.Folder) var mu sync.Mutex var wg sync.WaitGroup @@ -2717,6 +2756,9 @@ func fetchFoldersCmd(cfg *config.Config) tea.Cmd { defer wg.Done() folders, err := fetcher.FetchFolders(&acc) if err != nil { + mu.Lock() + errsByAccount[acc.ID] = err + mu.Unlock() return } mu.Lock() @@ -2739,6 +2781,7 @@ func fetchFoldersCmd(cfg *config.Config) tea.Cmd { return tui.FoldersFetchedMsg{ FoldersByAccount: foldersByAccount, MergedFolders: merged, + Errors: errsByAccount, } } } diff --git a/tui/messages.go b/tui/messages.go index 8a605eb0dd9c3b7a8ea519797c22228fcee711b9..207ecc5e4a7bbc97d634860ebe87e18f9b8a08df 100644 --- a/tui/messages.go +++ b/tui/messages.go @@ -416,9 +416,16 @@ type RequestRefreshMsg struct { // --- Folder Messages --- // FoldersFetchedMsg signals that IMAP folders have been fetched for all accounts. +// +// Errors holds per-account fetch failures (e.g. broken IMAP login, network +// unreachable). Accounts that succeeded appear in FoldersByAccount; accounts +// that failed appear in Errors. The two are disjoint by construction. This +// lets the TUI surface a non-fatal warning instead of silently dropping the +// affected account's folder list. type FoldersFetchedMsg struct { FoldersByAccount map[string][]fetcher.Folder // accountID -> folders MergedFolders []fetcher.Folder // unique folders across all accounts + Errors map[string]error // accountID -> fetch error, if any } // SwitchFolderMsg signals switching to a different IMAP folder.