From 19b34851ff5ffb31aef310686e74a4625404c2b3 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Tue, 7 Jul 2026 12:39:28 +0200 Subject: [PATCH] Guard the view-render and prompt-dismiss bounces on the generation The model-update bounces already drop themselves when the repo is switched mid-refresh (onUIThreadUnlessRepoChanged), but three bounces that touch the UI without writing the model did not: refreshView's render, the staging-panel refresh, and the stale continue-rebase prompt dismissal. All three ran unconditionally on the UI thread, so a background refresh in flight across a repo switch could render the old repo's data (through a context object belonging to the now-replaced context tree), or pop the new repo's popup based on the old repo's prompt state. Route them through onUIThreadUnlessRepoChanged too, so they're dropped alongside the model writes they accompany. This also fixes the dismiss bounce using the raw foreground OnUIThread, which ignored the background flag every other bounce in a background refresh respects. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/gui/controllers/helpers/refresh_helper.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index b1b3ef017..3c8524ffe 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -356,8 +356,9 @@ func (self *RefreshHelper) performRefresh(options types.RefreshOptions, calledFr // Bounce onto the UI thread so this runs after the files // scope's model-update bounce — RefreshStagingPanel reads // Model.Files (via Files.GetSelected) and would otherwise - // see the pre-refresh model. - self.onUIThread(env.background, func() error { + // see the pre-refresh model. Guard on the generation so a + // repo switch mid-refresh drops it, like the model bounces. + self.onUIThreadUnlessRepoChanged(env, func() error { self.stagingHelper.RefreshStagingPanel(types.OnFocusOpts{}) return nil }) @@ -1214,7 +1215,10 @@ func (self *RefreshHelper) refreshStateFiles(captured capturedFilesState, env re // appeared. Either way, a "continue?" prompt we're showing is now stale // (e.g. the operation was continued or aborted outside lazygit), so // dismiss it rather than leave the user with a prompt that would fail. - self.c.OnUIThread(func() error { + // Guard on the generation like the sibling PromptToContinueRebase + // bounce above: if the repo was switched while this refresh was in + // flight, a prompt showing now belongs to the new repo, so leave it be. + self.onUIThreadUnlessRepoChanged(env, func() error { self.mergeAndRebaseHelper.DismissContinueRebasePromptIfShowing() return nil }) @@ -1420,8 +1424,12 @@ func (self *RefreshHelper) refForLog() (string, *git_commands.BisectInfo) { func (self *RefreshHelper) refreshView(context types.Context, env refreshEnv) { // refreshView is called from the worker goroutine that drives async - // refreshes, so bounce to the UI thread before mutating view content. - self.onUIThread(env.background, func() error { + // refreshes, so bounce to the UI thread before mutating view content. Guard + // on the generation like the model-update bounces do: if the repo was + // switched while the refresh was in flight, its model write was already + // dropped, so there's nothing fresh to render — and the captured context + // belongs to the old repo's now-replaced context tree anyway. + self.onUIThreadUnlessRepoChanged(env, func() error { // Re-applying the filter must be done before re-rendering the view, so that // the filtered list model is up to date for rendering. self.searchHelper.ReApplyFilter(context)