Skip to content

fix(tui): surface fetchFolders errors#1267

Open
mvanhorn wants to merge 2 commits into
floatpane:masterfrom
mvanhorn:fix/fetch-folders-surface-per-account-errors
Open

fix(tui): surface fetchFolders errors#1267
mvanhorn wants to merge 2 commits into
floatpane:masterfrom
mvanhorn:fix/fetch-folders-surface-per-account-errors

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

@mvanhorn mvanhorn commented May 10, 2026

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."

How

File Change
tui/messages.go New Errors map[string]error field on FoldersFetchedMsg.
main.go (producer at fetchFoldersCmd) Capture per-account fetch errors into a sibling map under the existing mutex; pass through to the message.
main.go (consumer at FoldersFetchedMsg case) When Errors is non-empty, surface a transient overlay listing affected accounts and their errors.

The producer change is a 7-line localized capture: same goroutine, same
mutex, same return-after-error path -- the only difference is that the
err is stored before returning instead of being thrown away.

The consumer surfaces errors using the same pattern the codebase already
uses for PluginNotifyMsg: save m.previousModel, swap m.current to
a tui.NewStatus(...) overlay, fire tea.Tick(4*time.Second, ...) that
returns RestoreViewMsg. After 4 seconds the user is back to where
they were. No new TUI primitives, no new message types beyond the field
on the existing message.

Account display name lookup prefers Email -> Name -> ID so
partially configured accounts still show something meaningful in the
overlay. Errors are sorted alphabetically so the rendered list is
deterministic across restarts (the previous, per-iteration map order
would jitter).

The disjoint property (an account is in either FoldersByAccount or
Errors, never both) is preserved by the existing return after
recording the error.

Verification

make lint     # go fmt ./... + go vet ./...; clean
make test     # all packages pass
go build ./... # clean

I did not add automated tests for the TUI surface itself -- the existing
test suite has no tests for main.go's message handlers (verified via
find . -name '*_test.go'), so introducing the first one felt like
scope creep for a fix PR. Happy to add a coverage test for the
disjoint-by-construction property in fetchFoldersCmd if you'd like;
just point me at the testing pattern you'd want.

mvanhorn and others added 2 commits May 8, 2026 03:52
Closes floatpane#1251

GetCachedEmailBody is a read operation but updated LastAccessedAt
and rewrote the cache file on every hit, violating the
read-doesn't-mutate boundary called out in floatpane#1251. main.go calls
GetCachedEmailBody twice per email view (in UpdatePreviewMsg and
ViewEmailMsg) and both call sites already chase the cached read
with SaveEmailBody, which is the function responsible for stamping
LastAccessedAt and persisting. Removing the duplicate work here
also removes one disk write per email view.

Drops the two lines flagged in the issue (config/cache.go:505-506)
and adds a one-line note above the function pointing readers at
SaveEmailBody as the access-time owner.
… dropping

fetchFoldersCmd spawns one goroutine per account, each calling
fetcher.FetchFolders. The previous implementation discarded any
error returned by FetchFolders -- on broken IMAP login, expired
OAuth, or unreachable server, the affected account silently
dropped out of the merged folder list and the user got no signal.
The reporter (issue floatpane#1125) flagged this as a UX bug: the user
sees their folder list miss entries with no explanation.

Collect per-account errors into a sibling map and pass them
through FoldersFetchedMsg so the TUI can decide what to do with
them. The consumer at main.go:510 now surfaces non-empty errors
as a transient overlay (4s, then auto-restore) using the same
PluginNotifyMsg pattern the rest of the TUI uses for transient
status: save the previous model, swap to a Status overlay, fire
RestoreViewMsg via tea.Tick.

Account display name lookup prefers Email -> Name -> ID so the
error string shows something meaningful even on partially
configured accounts. Errors are sorted alphabetically so the
overlay is deterministic across restarts.

The disjoint-by-construction property (an account either
populates FoldersByAccount or Errors, never both) is preserved
by the existing return-after-error in the goroutine.

Closes floatpane#1125.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mvanhorn mvanhorn requested a review from a team as a code owner May 10, 2026 10:45
Copy link
Copy Markdown
Member

@floatpanebot floatpanebot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mvanhorn! Please fix the following issues with your PR:

  • Title: Is too long (78 characters). The PR title must be strictly under 40 characters.
  • Body: Missing the ## What? or ## Why? headings required by the PR template.

@floatpanebot floatpanebot added area/tui Terminal UI / view layer area/config Configuration / settings bug Something isn't working ci CI / build pipeline labels May 10, 2026
@mvanhorn mvanhorn changed the title fix(tui): surface per-account folder fetch errors instead of silently dropping fix(tui): surface fetchFolders errors May 10, 2026
@floatpanebot floatpanebot dismissed their stale review May 10, 2026 10:56

Formatting issues have been resolved. Thank you!

@andrinoff andrinoff added the size/M Diff: 51–200 lines label May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Configuration / settings area/tui Terminal UI / view layer bug Something isn't working ci CI / build pipeline size/M Diff: 51–200 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: TUI fetchFoldersCmd silently drops per-account errors

3 participants