From 9ab3be80ef591d9289d3613d43dbc22dff8fa2fb Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 26 Jun 2026 11:59:07 -0700 Subject: [PATCH] Dedupe connection error bars into a single thin top banner A connection/auth failure rendered two error bars: the global top-window InfoBar plus an in-page "Connection Error" InfoBar on the Connection page. The auth pipeline also published two global notifications for one failure, which forced the banner action to degrade to "Show more". Remove the in-page AuthErrorBar and consolidate to one top banner that: - carries the gateway/node error states the in-page bar covered - always routes the user to the Connection page via an "Open Connection" action (connection notifications are prioritized as the visible banner, preferring an actionable one over an action-less transient) - renders as a single thin line: bold headline + " - detail", with the action as a right-aligned hyperlink (Windows-aligned InfoBar) Other changes: - Stop publishing the duplicate connection:authentication-failed banner; the snapshot-driven connection:issue notification is the single source. - Re-home transient errors: WSL host-action failures use the inline card status (or a top notification when the card is hidden); connect/switch failures publish via ShowTransientConnectionError on the same banner id. - Remove now-dead resw strings (ConnectionPage_AuthGuidance*, ConnectionPage_ConnectFailed) across all five locales. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/OpenClaw.Tray.WinUI/App.xaml.cs | 64 +++++++++++----- .../Pages/ConnectionPage.xaml | 6 -- .../Pages/ConnectionPage.xaml.cs | 60 ++++++--------- .../Services/AppNotificationService.cs | 32 +++++++- .../Strings/en-us/Resources.resw | 34 --------- .../Strings/fr-fr/Resources.resw | 34 --------- .../Strings/nl-nl/Resources.resw | 34 --------- .../Strings/zh-cn/Resources.resw | 34 --------- .../Strings/zh-tw/Resources.resw | 34 --------- .../Windows/HubWindow.xaml | 22 ++++-- .../Windows/HubWindow.xaml.cs | 43 ++++++++--- .../Services/AppNotificationServiceTests.cs | 76 +++++++++++++++++++ 12 files changed, 218 insertions(+), 255 deletions(-) diff --git a/src/OpenClaw.Tray.WinUI/App.xaml.cs b/src/OpenClaw.Tray.WinUI/App.xaml.cs index 4c2f57162..8199f1838 100644 --- a/src/OpenClaw.Tray.WinUI/App.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/App.xaml.cs @@ -200,7 +200,6 @@ public IntPtr GetHubWindowHandle() private ToastService? _toastService; private AppNotificationService? _appNotificationService; internal AppNotificationService? AppNotifications => _appNotificationService; - private string? _lastAuthFailureNotificationMessage; private string? _lastConnectionIssueNotificationKey; private readonly Dictionary _reportedChannelIssueSignatures = new(StringComparer.OrdinalIgnoreCase); private string? _lastSandboxRiskNotificationKey; @@ -2332,6 +2331,37 @@ private void ShowPairingRejectedAppNotification(string deviceId, string? detail) id: BuildPairingRejectedNotificationId(deviceId)); } + /// + /// Publishes an immediate connection-error banner using the single + /// connection-issue notification identity. Used for transient, page-driven + /// failures (e.g. a manual gateway switch that throws) where the snapshot + /// may be briefly silent. Because it reuses the connection-issue id/dedupe + /// key it occupies the same banner slot — it cannot produce a second bar — + /// and the snapshot-driven path will replace or dismiss it on the next tick. + /// + internal void ShowTransientConnectionError(string message) + { + var body = string.IsNullOrWhiteSpace(message) + ? LocalizationHelper.GetString("AppNotification_GatewayConnectionFailed_DefaultMessage") + : message; + + // Keep the snapshot-driven publisher from immediately re-emitting a + // duplicate for the same underlying error. + _lastConnectionIssueNotificationKey = $"operator-error:{message}"; + + AppNotificationPublisher.Show( + _appNotificationService, + LocalizationHelper.GetString("AppNotification_GatewayConnectionFailed_Title"), + body, + "connection", + "lifecycle", + AppNotificationSeverity.Error, + ConnectionIssueNotificationDedupeKey, + "connection", + LocalizationHelper.GetString("AppNotification_ActionOpenConnection"), + id: ConnectionIssueNotificationId); + } + private void UpdateConnectionIssueNotification(GatewayConnectionSnapshot snapshot) { if (!TryBuildConnectionIssueNotification(snapshot, out var title, out var message, out var severity, out var category, out var key)) @@ -2375,9 +2405,12 @@ private static bool TryBuildConnectionIssueNotification( if (snapshot.OverallState == OverallConnectionState.Error) { title = LocalizationHelper.GetString("AppNotification_GatewayConnectionFailed_Title"); - message = snapshot.OperatorError ?? LocalizationHelper.GetString("AppNotification_GatewayConnectionFailed_DefaultMessage"); + var rawError = snapshot.OperatorError; + message = string.IsNullOrWhiteSpace(rawError) + ? LocalizationHelper.GetString("AppNotification_GatewayConnectionFailed_DefaultMessage") + : rawError; severity = AppNotificationSeverity.Error; - key = $"operator-error:{message}"; + key = $"operator-error:{rawError ?? "default"}"; return true; } @@ -2663,8 +2696,6 @@ private void OnGatewayConnectionStatusChanged(object? sender, ConnectionStatus s if (status == ConnectionStatus.Connected && _appState != null) { _appState.AuthFailureMessage = null; - _lastAuthFailureNotificationMessage = null; - _appNotificationService?.Dismiss("connection:authentication-failed"); } UpdateTrayIcon(); @@ -2714,27 +2745,18 @@ private void OnGatewayAuthenticationFailed(object? sender, string message) { UpdateTrayIcon(); - // Store auth failure in AppState — HubWindow observes it via PropertyChanged + // Store auth failure in AppState — observed for tray tooltip / status. if (_appState != null) { _appState.AuthFailureMessage = message; } - if (!string.Equals(_lastAuthFailureNotificationMessage, message, StringComparison.Ordinal)) - { - _lastAuthFailureNotificationMessage = message; - AppNotificationPublisher.Show( - _appNotificationService, - LocalizationHelper.GetString("AppNotification_GatewayAuthenticationFailed_Title"), - message, - "connection", - "authentication", - AppNotificationSeverity.Error, - "connection:authentication-failed", - "connection", - LocalizationHelper.GetString("AppNotification_ActionOpenConnection"), - id: "connection:authentication-failed"); - } + // The user-facing banner is published by the single connection-issue + // notification (UpdateConnectionIssueNotification), driven off the + // snapshot's Error state + OperatorError (same string surfaced here). + // Publishing a second "authentication failed" banner here produced a + // duplicate top bar and forced the action button to degrade to + // "Show more", so it is intentionally not raised from this handler. } private void OnGatewaySessionCommandCompleted(object? sender, SessionCommandResult result) diff --git a/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml index 7c9b1d03f..a5581d83a 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml +++ b/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml @@ -59,12 +59,6 @@ PINNED MODIFIERS — render across every mode ═════════════════════════════════════════════════════════ --> - - - _hiddenNotificationIds = new(StringComparer.Ordinal); public AppNotification? SelectVisibleNotification(AppNotificationSnapshot snapshot, bool revealHiddenIfNeeded = false) { PruneRemovedNotifications(snapshot); - var visible = snapshot.ActiveNotifications.FirstOrDefault(notification => - !_hiddenNotificationIds.Contains(notification.Id)); + + var visibleCandidates = snapshot.ActiveNotifications + .Where(notification => !_hiddenNotificationIds.Contains(notification.Id)) + .ToList(); + + // Connection issues are the most actionable banner (they route the user + // to the Connection page), so surface them ahead of any earlier, + // unrelated notification that happens to be current. Without this, a + // connection failure arriving while another notification is showing + // would be queued behind it and the user couldn't reach Connection. + // Among connection-source notifications, prefer one that actually has an + // action: an action-less connection notification (e.g. a transient + // gateway-host failure) must not mask a real connection error and + // degrade the banner to "Show more". + var visible = visibleCandidates.FirstOrDefault(IsActionableConnectionPriority) + ?? visibleCandidates.FirstOrDefault(IsConnectionPriority) + ?? visibleCandidates.FirstOrDefault(); if (visible is not null || !revealHiddenIfNeeded) return visible; - var fallback = snapshot.ActiveNotifications.FirstOrDefault(); + var fallback = snapshot.ActiveNotifications.FirstOrDefault(IsActionableConnectionPriority) + ?? snapshot.ActiveNotifications.FirstOrDefault(IsConnectionPriority) + ?? snapshot.ActiveNotifications.FirstOrDefault(); if (fallback is not null) _hiddenNotificationIds.Remove(fallback.Id); return fallback; } + private static bool IsConnectionPriority(AppNotification notification) => + string.Equals(notification.Source, ConnectionSource, StringComparison.OrdinalIgnoreCase); + + private static bool IsActionableConnectionPriority(AppNotification notification) => + IsConnectionPriority(notification) + && !string.IsNullOrWhiteSpace(notification.ActionLabel) + && !string.IsNullOrWhiteSpace(notification.ActionRoute); + public void HideActiveNotifications(AppNotificationSnapshot snapshot) { PruneRemovedNotifications(snapshot); diff --git a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw index 02652edf6..6ce7d3a6d 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw @@ -1815,9 +1815,6 @@ On your gateway host (Mac/Linux), run: Gateway endpoint used by both the operator client and node service. - - Connection Error - Disconnected @@ -3276,9 +3273,6 @@ On your gateway host (Mac/Linux), run: Open Cron - - Gateway authentication failed - Gateway connection failed @@ -5599,9 +5593,6 @@ Make sure the gateway is running. Cancel - - Connect failed - Gateway connection failed. @@ -5632,31 +5623,6 @@ Make sure the gateway is running. Deny pairing request - - {0} - -Paste a new setup code from the Add Gateway flow. - - - {0} - -Your device needs approval on the gateway host. - - - {0} - -This gateway requires password authentication. - - - {0} - -The gateway may require a different auth protocol version. - - - {0} - -Check your connection settings and try again. - bootstrap diff --git a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw index 96ea77906..45705128b 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw @@ -1767,9 +1767,6 @@ Sur votre hôte passerelle (Mac/Linux), exécutez : Point de terminaison de passerelle utilisé par le client opérateur et le service de nœud. - - Erreur de connexion - Déconnecté @@ -3204,9 +3201,6 @@ Sur votre hôte passerelle (Mac/Linux), exécutez : Ouvrir Cron - - Échec de l'authentification de la passerelle - Échec de la connexion à la passerelle @@ -5551,9 +5545,6 @@ Assurez-vous que la passerelle est en cours d'exécution. Annuler - - Échec de la connexion - Échec de la connexion à la passerelle. @@ -5584,31 +5575,6 @@ Assurez-vous que la passerelle est en cours d'exécution. Refuser la demande d’appairage - - {0} - -Collez un nouveau code de configuration depuis le flux Ajouter une passerelle. - - - {0} - -Votre appareil nécessite une approbation sur l’hôte de la passerelle. - - - {0} - -Cette passerelle nécessite une authentification par mot de passe. - - - {0} - -La passerelle pourrait nécessiter une version différente du protocole d’authentification. - - - {0} - -Vérifiez vos paramètres de connexion et réessayez. - amorçage diff --git a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw index 01dde0c68..5a2b26bc8 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw @@ -1768,9 +1768,6 @@ Voer op uw gateway-host (Mac/Linux) uit: Gateway-eindpunt dat door zowel de operatorclient als de knooppuntservice wordt gebruikt. - - Verbindingsfout - Verbinding verbroken @@ -3205,9 +3202,6 @@ Voer op uw gateway-host (Mac/Linux) uit: Cron openen - - Gatewayverificatie mislukt - Gatewayverbinding mislukt @@ -5552,9 +5546,6 @@ Controleer of de gateway actief is. Annuleren - - Verbinding mislukt - Gatewayverbinding mislukt. @@ -5585,31 +5576,6 @@ Controleer of de gateway actief is. Koppelingverzoek weigeren - - {0} - -Plak een nieuwe installatiecode vanuit het Gateway toevoegen-proces. - - - {0} - -Uw apparaat moet worden goedgekeurd op de gatewayhost. - - - {0} - -Deze gateway vereist wachtwoordverificatie. - - - {0} - -De gateway vereist mogelijk een andere versie van het verificatieprotocol. - - - {0} - -Controleer uw verbindingsinstellingen en probeer het opnieuw. - bootstrap-modus diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw index 38e5ecbad..698ce6c04 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw @@ -1767,9 +1767,6 @@ 操作员客户端和节点服务共同使用的网关终结点。 - - 连接错误 - 已断开连接 @@ -3204,9 +3201,6 @@ 打开 Cron - - 网关身份验证失败 - 网关连接失败 @@ -5552,9 +5546,6 @@ 取消 - - 连接失败 - 网关连接失败。 @@ -5585,31 +5576,6 @@ 拒绝配对请求 - - {0} - -从添加网关流程中粘贴新的设置代码。 - - - {0} - -您的设备需要在网关主机上获得批准。 - - - {0} - -此网关需要密码身份验证。 - - - {0} - -网关可能需要不同版本的认证协议。 - - - {0} - -请检查您的连接设置并重试。 - 引导 diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw index 08258bfa4..43dbc909e 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw @@ -1767,9 +1767,6 @@ 操作員用戶端和節點服務共同使用的閘道端點。 - - 連線錯誤 - 已中斷連線 @@ -3204,9 +3201,6 @@ 開啟 Cron - - 閘道驗證失敗 - 閘道連線失敗 @@ -5552,9 +5546,6 @@ 取消 - - 連線失敗 - 閘道連線失敗。 @@ -5585,31 +5576,6 @@ 拒絕配對請求 - - {0} - -從新增閘道流程中貼上新的設定碼。 - - - {0} - -您的裝置需要在閘道主機上獲得核准。 - - - {0} - -此閘道需要密碼驗證。 - - - {0} - -閘道可能需要不同版本的認證協定。 - - - {0} - -請檢查您的連線設定並重試。 - 引導 diff --git a/src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml b/src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml index 1402ad533..7a97be51a 100644 --- a/src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml +++ b/src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml @@ -121,19 +121,25 @@ IsClosable="True" Closed="OnAppNotificationInfoBarClosed"> - + + -