From 28ff2b7b3b144dfd4714b9ba314cd730ab099871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Tue, 28 Jul 2026 17:00:43 +0200 Subject: [PATCH] [client] Dispatch status snapshots only to visible windows --- client/ui/main.go | 12 ++++++++++++ client/ui/services/daemon_feed.go | 24 ++++++++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/client/ui/main.go b/client/ui/main.go index 03e55a134..656acd61f 100644 --- a/client/ui/main.go +++ b/client/ui/main.go @@ -103,6 +103,18 @@ func main() { updaterHolder := updater.NewHolder(app.Event) update := services.NewUpdate(conn, updaterHolder) daemonFeed := services.NewDaemonFeed(conn, app.Event, updaterHolder, debugLog) + // Status snapshots go only to visible windows — a snapshot is full state, + // so a hidden webview loses nothing by being skipped; it gets the cached + // one on show (SetShowReplay below). Every other event stays on the bus. + daemonFeed.SetWindowDispatcher(func(st services.Status) { + ev := &application.CustomEvent{Name: services.EventStatusSnapshot, Data: st} + for _, w := range app.Window.GetAll() { + if w == nil || !w.IsVisible() { + continue + } + w.DispatchWailsEvent(ev) + } + }) notifier := notifications.New() compat := services.NewCompat(conn) // macOS shows no toast until permission is requested. Run it after diff --git a/client/ui/services/daemon_feed.go b/client/ui/services/daemon_feed.go index e8bd84e4f..082edfc7b 100644 --- a/client/ui/services/daemon_feed.go +++ b/client/ui/services/daemon_feed.go @@ -171,9 +171,10 @@ type DaemonFeed struct { // statusSubs are Go-side snapshot consumers (the tray), fed directly so // they don't ride the window event bus. Callbacks run synchronously on // the stream goroutine, so pushes arrive in order. - statusSubsMu sync.Mutex - statusSubs []func(Status) - lastStatus *Status + statusSubsMu sync.Mutex + statusSubs []func(Status) + lastStatus *Status + windowDispatcher func(Status) switchMu sync.Mutex switchInProgress bool @@ -204,16 +205,31 @@ func (s *DaemonFeed) OnStatus(cb func(Status)) { s.statusSubsMu.Unlock() } +// SetWindowDispatcher installs the frontend push path: it receives every +// snapshot and decides which webview windows get it (visible ones). While +// unset, pushStatus falls back to the event-bus broadcast. +func (s *DaemonFeed) SetWindowDispatcher(fn func(Status)) { + s.statusSubsMu.Lock() + s.windowDispatcher = fn + s.statusSubsMu.Unlock() +} + // pushStatus delivers a snapshot to the Go-side subscribers and the frontend, -// and caches it for LastStatus replays. +// and caches it for LastStatus replays. Hidden windows are skipped by the +// window dispatcher; they catch up via the show replay (WindowManager). func (s *DaemonFeed) pushStatus(st Status) { s.statusSubsMu.Lock() s.lastStatus = &st subs := slices.Clone(s.statusSubs) + dispatch := s.windowDispatcher s.statusSubsMu.Unlock() for _, cb := range subs { cb(st) } + if dispatch != nil { + dispatch(st) + return + } s.emitter.Emit(EventStatusSnapshot, st) }