Skip to content

Commit dad02f3

Browse files
authored
fix(tui): surface fetchFolders errors (#1267)
## What? Per-account `fetcher.FetchFolders` errors are now collected into `FoldersFetchedMsg.Errors` and surfaced as a transient overlay in the TUI (4s, then auto-restore via the existing `PluginNotifyMsg` pattern). Closes #1125. ## Why? `fetchFoldersCmd` spawns one goroutine per account, each calling `fetcher.FetchFolders`. The previous code returned immediately on err and the goroutine vanished without leaving a trace. If an account's IMAP login was broken, OAuth had expired, or the server was unreachable, the affected account silently dropped out of the merged folder list and the user got no signal -- their folder list quietly missed entries. The reporter described this exactly: "user sees the folder list silently miss those folders and never gets a notification."
1 parent f86d3a6 commit dad02f3

2 files changed

Lines changed: 50 additions & 0 deletions

File tree

main.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,44 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
549549
}
550550
go config.SaveAccountFolders(accID, names)
551551
}
552+
// Per-account fetch errors (e.g. broken IMAP login, unreachable
553+
// server) are non-fatal: other accounts' folders are still shown.
554+
// Surface them as a transient overlay so the user knows why an
555+
// account's folders are missing instead of silently dropping them.
556+
// Reuses the PluginNotifyMsg pattern (save current view, show
557+
// status with a tea.Tick that fires RestoreViewMsg).
558+
if len(msg.Errors) > 0 {
559+
lookup := map[string]string{}
560+
if m.config != nil {
561+
for _, acc := range m.config.Accounts {
562+
name := acc.Email
563+
if name == "" {
564+
name = acc.Name
565+
}
566+
if name == "" {
567+
name = acc.ID
568+
}
569+
lookup[acc.ID] = name
570+
}
571+
}
572+
parts := make([]string, 0, len(msg.Errors))
573+
for accID, err := range msg.Errors {
574+
name := lookup[accID]
575+
if name == "" {
576+
name = accID
577+
}
578+
parts = append(parts, fmt.Sprintf("%s: %v", name, err))
579+
}
580+
sort.Strings(parts)
581+
m.previousModel = m.current
582+
m.current = tui.NewStatus(fmt.Sprintf(
583+
"Folder fetch failed for %d account(s): %s",
584+
len(parts), strings.Join(parts, "; "),
585+
))
586+
return m, tea.Tick(4*time.Second, func(t time.Time) tea.Msg {
587+
return tui.RestoreViewMsg{}
588+
})
589+
}
552590
return m, nil
553591

554592
case tui.SwitchFolderMsg:
@@ -2707,6 +2745,7 @@ func fetchFoldersCmd(cfg *config.Config) tea.Cmd {
27072745
return nil
27082746
}
27092747
foldersByAccount := make(map[string][]fetcher.Folder)
2748+
errsByAccount := make(map[string]error)
27102749
seen := make(map[string]fetcher.Folder)
27112750
var mu sync.Mutex
27122751
var wg sync.WaitGroup
@@ -2717,6 +2756,9 @@ func fetchFoldersCmd(cfg *config.Config) tea.Cmd {
27172756
defer wg.Done()
27182757
folders, err := fetcher.FetchFolders(&acc)
27192758
if err != nil {
2759+
mu.Lock()
2760+
errsByAccount[acc.ID] = err
2761+
mu.Unlock()
27202762
return
27212763
}
27222764
mu.Lock()
@@ -2739,6 +2781,7 @@ func fetchFoldersCmd(cfg *config.Config) tea.Cmd {
27392781
return tui.FoldersFetchedMsg{
27402782
FoldersByAccount: foldersByAccount,
27412783
MergedFolders: merged,
2784+
Errors: errsByAccount,
27422785
}
27432786
}
27442787
}

tui/messages.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,9 +416,16 @@ type RequestRefreshMsg struct {
416416
// --- Folder Messages ---
417417

418418
// FoldersFetchedMsg signals that IMAP folders have been fetched for all accounts.
419+
//
420+
// Errors holds per-account fetch failures (e.g. broken IMAP login, network
421+
// unreachable). Accounts that succeeded appear in FoldersByAccount; accounts
422+
// that failed appear in Errors. The two are disjoint by construction. This
423+
// lets the TUI surface a non-fatal warning instead of silently dropping the
424+
// affected account's folder list.
419425
type FoldersFetchedMsg struct {
420426
FoldersByAccount map[string][]fetcher.Folder // accountID -> folders
421427
MergedFolders []fetcher.Folder // unique folders across all accounts
428+
Errors map[string]error // accountID -> fetch error, if any
422429
}
423430

424431
// SwitchFolderMsg signals switching to a different IMAP folder.

0 commit comments

Comments
 (0)