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) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-07-07 12:39:28 +02:00
parent 2c3a6acafa
commit 19b34851ff

View file

@ -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)