From 2070a1b9ceb88e628ac5a068db92e4cabf301037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Tue, 18 Aug 2026 15:16:53 +0200 Subject: [PATCH] [client] Defer session-expired login trigger until the main frontend is ready On a cold start the expired-session tray row created the main window and emitted EventTriggerLogin immediately, before the React app had mounted and subscribed, so the login flow silently did nothing. The WindowManager now queues events per window and flushes them in markReady, mirroring the pendingTab handling for settings. --- client/ui/services/windowmanager.go | 30 +++++++++++++++++++++++++++++ client/ui/tray.go | 9 +++++++++ client/ui/tray_session.go | 3 +-- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/client/ui/services/windowmanager.go b/client/ui/services/windowmanager.go index 73d1cafb7..ba69a597b 100644 --- a/client/ui/services/windowmanager.go +++ b/client/ui/services/windowmanager.go @@ -119,6 +119,7 @@ type WindowManager struct { ready map[uint]bool showPending map[uint]bool pendingTab map[uint]string + pendingEmits map[uint][]string fallbackTimers map[uint]*time.Timer // recenterOnShow is set only on the minimal-WM/XEmbed path, where the WM neither centers nor // restores position; nil on full desktops so re-centering can't fight a user-moved window. @@ -135,6 +136,7 @@ func NewWindowManager(app *application.App, mainWindow *application.WebviewWindo ready: map[uint]bool{}, showPending: map[uint]bool{}, pendingTab: map[uint]string{}, + pendingEmits: map[uint][]string{}, fallbackTimers: map[uint]*time.Timer{}, } s.watchPainted() @@ -470,6 +472,27 @@ func (s *WindowManager) ShowMain() { s.showWhenReady(s.MainWindow()) } +// ShowMainAndEmit brings the main window forward and emits event once its frontend is ready. +func (s *WindowManager) ShowMainAndEmit(event string) { + w := s.MainWindow() + if w == nil { + return + } + + id := w.ID() + s.mu.Lock() + ready := s.ready[id] + if !ready { + s.pendingEmits[id] = append(s.pendingEmits[id], event) + } + s.mu.Unlock() + + s.showWhenReady(w) + if ready { + s.app.Event.Emit(event) + } +} + func (s *WindowManager) MainWindow() *application.WebviewWindow { s.mu.Lock() factory := s.newMain @@ -533,6 +556,7 @@ func (s *WindowManager) forgetWindowLocked(w *application.WebviewWindow) { delete(s.ready, id) delete(s.showPending, id) delete(s.pendingTab, id) + delete(s.pendingEmits, id) kept := s.hiddenForLogin[:0] for _, hidden := range s.hiddenForLogin { @@ -563,12 +587,14 @@ func (s *WindowManager) markReady(w *application.WebviewWindow) { s.ready[id] = true wanted := s.showPending[id] tab, hasTab := s.pendingTab[id] + emits := s.pendingEmits[id] if timer := s.fallbackTimers[id]; timer != nil { timer.Stop() delete(s.fallbackTimers, id) } delete(s.showPending, id) delete(s.pendingTab, id) + delete(s.pendingEmits, id) s.mu.Unlock() if already { @@ -582,6 +608,10 @@ func (s *WindowManager) markReady(w *application.WebviewWindow) { if wanted { s.showNow(w) } + + for _, event := range emits { + s.app.Event.Emit(event) + } } func (s *WindowManager) showWhenReady(w *application.WebviewWindow) { diff --git a/client/ui/tray.go b/client/ui/tray.go index d27ebca20..437cbeabc 100644 --- a/client/ui/tray.go +++ b/client/ui/tray.go @@ -284,6 +284,15 @@ func (t *Tray) showMain() { } } +func (t *Tray) showMainAndEmit(event string) { + if t.svc.WindowManager != nil { + t.svc.WindowManager.ShowMainAndEmit(event) + return + } + t.showMain() + t.app.Event.Emit(event) +} + // applyLanguage re-renders every translated surface in the Localizer's current // language. Wails dispatches menu/tray APIs onto the UI thread internally, so // calling them from the Localizer's background goroutine is safe; profileLoadMu diff --git a/client/ui/tray_session.go b/client/ui/tray_session.go index e0dec949f..6e5d07740 100644 --- a/client/ui/tray_session.go +++ b/client/ui/tray_session.go @@ -304,8 +304,7 @@ func (t *Tray) openSessionExtendFlow() { } seconds := int(time.Until(deadline).Seconds()) if seconds <= 0 { - t.showMain() - t.app.Event.Emit(services.EventTriggerLogin) + t.showMainAndEmit(services.EventTriggerLogin) return } if t.svc.WindowManager == nil {