Create popups and menus on the UI thread

Raising a popup or menu pushes a context and mutates the popup views, so
it must happen on the UI thread. But it can be triggered from a worker
goroutine — for example a WithWaitingStatus handler that hits a merge
conflict and calls PromptForConflictHandling, or a worker that shows a
confirmation — where it raced the UI thread's layout and draw code.

Bounce the creation onto the UI thread at the one point where the popup
and menu producers are injected into the popup handler, so every caller
stays oblivious to the threading. For a caller that is already on the UI
thread this adds no delay: the main loop drains the enqueued closure in
the same event-processing cycle, before it draws.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-07-17 12:35:54 +02:00
co-authored by Claude Opus 4.8
parent eda2151330
commit 9754a77b64
+14 -2
View File
@@ -812,13 +812,25 @@ func NewGui(
gui.PopupHandler = popup.NewPopupHandler(
cmn,
// Raising a popup or menu pushes a context and mutates the popup views,
// and it can be triggered from a worker goroutine (e.g. a
// WithWaitingStatus handler that hits a merge conflict and asks the user
// how to proceed). Bounce the creation onto the UI thread so it can't
// race the layout/draw code. Doing it here, at the one point where these
// producers are injected, keeps every caller oblivious to the threading.
func(ctx goContext.Context, opts types.CreatePopupPanelOpts) {
gui.helpers.Confirmation.CreatePopupPanel(ctx, opts)
gui.onUIThread(func() error {
gui.helpers.Confirmation.CreatePopupPanel(ctx, opts)
return nil
})
},
func() error { gui.c.Refresh(types.RefreshOptions{}); return nil },
func() { gui.State.ContextMgr.Pop() },
func() types.Context { return gui.State.ContextMgr.Current() },
gui.createMenu,
func(opts types.CreateMenuOptions) error {
gui.onUIThread(func() error { return gui.createMenu(opts) })
return nil
},
func(message string, f func(gocui.Task) error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) },
func(message string, f func(gocui.Task) error) {
gui.helpers.AppStatus.WithWaitingStatusBlockingInput(message, f)