diff --git a/src/OpenClaw.Tray.WinUI/Helpers/NavOriginLabels.cs b/src/OpenClaw.Tray.WinUI/Helpers/NavOriginLabels.cs new file mode 100644 index 000000000..25d6f7143 --- /dev/null +++ b/src/OpenClaw.Tray.WinUI/Helpers/NavOriginLabels.cs @@ -0,0 +1,45 @@ +namespace OpenClawTray.Helpers; + +/// +/// Maps HubWindow nav tags to localised page labels for cross-page +/// "Back to {origin}" affordances. Reuses the sidebar +/// HubWindow_NavigationViewItem_*.Content resw entries so page names +/// are translated once and stay in sync with the sidebar. +/// +internal static class NavOriginLabels +{ + public static string DisplayLabel(string? tag) + { + if (string.IsNullOrWhiteSpace(tag)) return string.Empty; + var resourceKey = tag switch + { + "chat" => "HubWindow_NavigationViewItem_82.Content", + "connection" => "HubWindow_NavigationViewItem_88.Content", + "sessions" => "HubWindow_NavigationViewItem_91.Content", + "skills" => "HubWindow_NavigationViewItem_97.Content", + "channels" => "HubWindow_NavigationViewItem_109.Content", + "instances" or "nodes" => "HubWindow_NavigationViewItem_112.Content", + "agentevents" => "HubWindow_NavigationViewItem_94.Content", + "bindings" => "HubWindow_NavigationViewItem_115.Content", + "config" => "HubWindow_NavigationViewItem_118.Content", + "usage" => "HubWindow_NavigationViewItem_121.Content", + "cron" => "HubWindow_NavigationViewItem_124.Content", + "voice" => "HubWindow_NavigationViewItem_Voice.Content", + "settings" => "HubWindow_NavigationViewItem_133.Content", + "permissions" => "HubWindow_NavigationViewItem_136.Content", + "sandbox" => "HubWindow_NavigationViewItem_Sandbox.Content", + "debug" => "HubWindow_NavigationViewItem_145.Content", + "info" or "about" => "HubWindow_NavigationViewItem_148.Content", + _ => null, + }; + return resourceKey == null + ? char.ToUpperInvariant(tag![0]) + tag.Substring(1) + : LocalizationHelper.GetString(resourceKey); + } + + public static string BackToLabel(string? tag) => + LocalizationHelper.Format("BackToOriginFormat", DisplayLabel(tag)); +} + + + diff --git a/src/OpenClaw.Tray.WinUI/Pages/AboutPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/AboutPage.xaml.cs index d9f9f0eae..d57c4b534 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/AboutPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/AboutPage.xaml.cs @@ -138,7 +138,7 @@ private void OnCheckUpdatesClick(object sender, RoutedEventArgs e) private void OnMoreDiagnosticsClick(object sender, RoutedEventArgs e) { - ((IAppCommands)CurrentApp).Navigate("debug"); + ((IAppCommands)CurrentApp).Navigate("debug", "info"); } private void OnDocumentationClick(object sender, RoutedEventArgs e) diff --git a/src/OpenClaw.Tray.WinUI/Pages/BindingsPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/BindingsPage.xaml.cs index 6f68d2b29..aab9fb29f 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/BindingsPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/BindingsPage.xaml.cs @@ -56,7 +56,7 @@ public void Initialize() } private void OnOpenConnectionClick(object sender, RoutedEventArgs e) - => ((IAppCommands)CurrentApp).Navigate("connection"); + => ((IAppCommands)CurrentApp).Navigate("connection", "bindings"); private void OnAppStateChanged(object? sender, PropertyChangedEventArgs e) { diff --git a/src/OpenClaw.Tray.WinUI/Pages/ChannelsPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/ChannelsPage.xaml.cs index fc8e18542..fea4c4a1d 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/ChannelsPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/ChannelsPage.xaml.cs @@ -1346,7 +1346,7 @@ private FrameworkElement BuildInlineConfigForm(ChannelRecord record) { Content = "Open Config page", }; - openConfigBtn.Click += (_, _) => ((IAppCommands)CurrentApp).Navigate("config"); + openConfigBtn.Click += (_, _) => ((IAppCommands)CurrentApp).Navigate("config", "channels"); actionRow.Children.Add(saveBtn); actionRow.Children.Add(openConfigBtn); stack.Children.Add(actionRow); @@ -1946,7 +1946,7 @@ private FrameworkElement BuildConfigPlaceholder(ChannelRecord record) }; btn.Click += (_, _) => { - ((IAppCommands)CurrentApp).Navigate("config"); + ((IAppCommands)CurrentApp).Navigate("config", "channels"); }; stack.Children.Add(btn); return stack; diff --git a/src/OpenClaw.Tray.WinUI/Pages/ChatPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/ChatPage.xaml.cs index 37aaa0f53..9a8ebb9d0 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/ChatPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/ChatPage.xaml.cs @@ -266,7 +266,7 @@ private void ShowFunctionalSurface() onStopSpeaking: () => app?.StopChatSpeaking(), onVoiceRequest: VoiceTranscribeAsync, onAttachClick: OnAttachClicked, - onSettingsClick: () => _hub?.NavigateTo("voice"), + onSettingsClick: () => _hub?.NavigateTo("voice", "chat"), onSpeakerMuteChanged: muted => (App.Current as App)?.SetChatSpeakerMuted(muted), initialMuted: CurrentApp.Settings?.VoiceTtsEnabled == false, suppressAutoDispose: true); @@ -771,7 +771,7 @@ private async Task ShowVoiceSettingsDialogAsync(string title, string message, Ac private void NavigateToVoiceSettings() { if (_hub is not null) - _hub.NavigateTo("voice"); + _hub.NavigateTo("voice", "chat"); else (App.Current as App)?.ShowHub("voice"); } diff --git a/src/OpenClaw.Tray.WinUI/Pages/ConfigPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/ConfigPage.xaml index d49cd0bb6..28b60b60e 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/ConfigPage.xaml +++ b/src/OpenClaw.Tray.WinUI/Pages/ConfigPage.xaml @@ -17,8 +17,19 @@ - - + + + + + + + + + @@ -45,6 +56,7 @@ + + + + + + + + + diff --git a/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs index c1ffb3156..ab4a3301d 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs @@ -157,6 +157,31 @@ public void Initialize() UpdatePairingRequests(existingNode); if (_appState?.DevicePairList is { } existingDevice) UpdateDevicePairingRequests(existingDevice); + + RefreshBackOriginLink(); + } + + private string? _backOriginTag; + + private void RefreshBackOriginLink() + { + var hub = CurrentApp.ActiveHubWindow as OpenClawTray.Windows.HubWindow; + var origin = hub?.LastNavigationOrigin; + if (string.IsNullOrEmpty(origin)) + { + _backOriginTag = null; + BackOriginLink.Visibility = Visibility.Collapsed; + return; + } + _backOriginTag = origin; + BackOriginText.Text = Helpers.NavOriginLabels.BackToLabel(origin); + BackOriginLink.Visibility = Visibility.Visible; + } + + private void OnBackOriginClicked(object sender, RoutedEventArgs e) + { + if (!string.IsNullOrEmpty(_backOriginTag)) + ((IAppCommands)CurrentApp).Navigate(_backOriginTag); } private void OnPageUnloaded(object sender, RoutedEventArgs e) diff --git a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml.cs index 2928e827a..1e948124a 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/CronPage.xaml.cs @@ -80,7 +80,7 @@ public void Initialize() } private void OnOpenConnectionClick(object sender, RoutedEventArgs e) - => ((IAppCommands)CurrentApp).Navigate("connection"); + => ((IAppCommands)CurrentApp).Navigate("connection", "cron"); private void OnRefreshClick(object sender, RoutedEventArgs e) { diff --git a/src/OpenClaw.Tray.WinUI/Pages/DebugPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/DebugPage.xaml index 57f61e3e0..e9cd20659 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/DebugPage.xaml +++ b/src/OpenClaw.Tray.WinUI/Pages/DebugPage.xaml @@ -50,6 +50,18 @@ Padding="24,24,24,24" Spacing="{StaticResource DiagSettingsCardSpacing}"> + + + + + + + + ((IAppCommands)CurrentApp).Navigate("connection"); + => ((IAppCommands)CurrentApp).Navigate("connection", "debug"); // ── Detail view (recent log) ───────────────────────────────────── diff --git a/src/OpenClaw.Tray.WinUI/Pages/InstancesPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/InstancesPage.xaml.cs index d08d486f4..342941f57 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/InstancesPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/InstancesPage.xaml.cs @@ -126,7 +126,7 @@ private void UpdatePendingPairBanner() } private void OnPendingPairBannerClicked(object sender, RoutedEventArgs e) - => ((IAppCommands)CurrentApp).Navigate("connection"); + => ((IAppCommands)CurrentApp).Navigate("connection", "instances"); public void UpdateNodes(GatewayNodeInfo[] nodes) { diff --git a/src/OpenClaw.Tray.WinUI/Pages/PermissionsPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/PermissionsPage.xaml.cs index d8d165de5..fc81ce036 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/PermissionsPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/PermissionsPage.xaml.cs @@ -362,7 +362,7 @@ private void UpdateSttEngineHint() private void OnSttMoreSettingsClick(object sender, RoutedEventArgs e) { - ((IAppCommands)CurrentApp).Navigate("voice"); + ((IAppCommands)CurrentApp).Navigate("voice", "permissions"); } // ── Text-to-Speech card ────────────────────────────────────────── diff --git a/src/OpenClaw.Tray.WinUI/Pages/SessionsPage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/SessionsPage.xaml.cs index f47d80935..17d481239 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/SessionsPage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/SessionsPage.xaml.cs @@ -246,7 +246,8 @@ private void OnOpenChat(object sender, RoutedEventArgs e) { hub.PendingChatSessionKey = key; } - ((IAppCommands)CurrentApp).Navigate("chat", "sessions"); + // No origin tag: opening chat should not surface a "Back to Sessions" link. + ((IAppCommands)CurrentApp).Navigate("chat"); } } diff --git a/src/OpenClaw.Tray.WinUI/Pages/UsagePage.xaml.cs b/src/OpenClaw.Tray.WinUI/Pages/UsagePage.xaml.cs index c2ad6b89c..7d9576a2b 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/UsagePage.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/Pages/UsagePage.xaml.cs @@ -168,7 +168,7 @@ private void RequestRefresh(IOperatorGatewayClient client) } private void OnOpenConnectionClick(object sender, RoutedEventArgs e) - => ((IAppCommands)CurrentApp).Navigate("connection"); + => ((IAppCommands)CurrentApp).Navigate("connection", "usage"); private void OnAppStateChanged(object? sender, PropertyChangedEventArgs e) { diff --git a/src/OpenClaw.Tray.WinUI/Pages/VoiceSettingsPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/VoiceSettingsPage.xaml index c38639ec0..529666d79 100644 --- a/src/OpenClaw.Tray.WinUI/Pages/VoiceSettingsPage.xaml +++ b/src/OpenClaw.Tray.WinUI/Pages/VoiceSettingsPage.xaml @@ -8,6 +8,18 @@ + + + + + + + + diff --git a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw index 8f22d4729..9481d5477 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw @@ -3255,6 +3255,14 @@ On your gateway host (Mac/Linux), run: Back to Connection + + Back to {0} + Format string for cross-page back link text. {0} is the localised name of the page the user came from (reused from the sidebar HubWindow_NavigationViewItem_* labels). + + + Back + Fallback label for the inline back link when the origin page is unknown. Normally not shown — the link is hidden when there is no origin. + 🔗 Pending Operator/Node Pairing diff --git a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw index 10d0ab868..e3b9c87ce 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw @@ -3207,6 +3207,14 @@ Sur votre hôte passerelle (Mac/Linux), exécutez : Retour à la connexion + + Retour à {0} + Format string for cross-page back link text. {0} is the localised name of the page the user came from (reused from the sidebar HubWindow_NavigationViewItem_* labels). + + + Retour + Fallback label for the inline back link when the origin page is unknown. Normally not shown — the link is hidden when there is no origin. + 🔗 Pending Operator/Node Pairing diff --git a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw index 251ef4f01..f8563b416 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw @@ -3208,6 +3208,14 @@ Voer op uw gateway-host (Mac/Linux) uit: Terug naar verbinding + + Terug naar {0} + Format string for cross-page back link text. {0} is the localised name of the page the user came from (reused from the sidebar HubWindow_NavigationViewItem_* labels). + + + Terug + Fallback label for the inline back link when the origin page is unknown. Normally not shown — the link is hidden when there is no origin. + 🔗 Pending Operator/Node Pairing diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw index 889455944..fd88a35db 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw @@ -3207,6 +3207,14 @@ 返回连接 + + 返回 {0} + Format string for cross-page back link text. {0} is the localised name of the page the user came from (reused from the sidebar HubWindow_NavigationViewItem_* labels). + + + 返回 + Fallback label for the inline back link when the origin page is unknown. Normally not shown — the link is hidden when there is no origin. + 🔗 Pending Operator/Node Pairing diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw index 4e8bbefdc..42583420e 100644 --- a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw +++ b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw @@ -3207,6 +3207,14 @@ 返回連線 + + 返回 {0} + Format string for cross-page back link text. {0} is the localised name of the page the user came from (reused from the sidebar HubWindow_NavigationViewItem_* labels). + + + 返回 + Fallback label for the inline back link when the origin page is unknown. Normally not shown — the link is hidden when there is no origin. + 🔗 Pending Operator/Node Pairing diff --git a/tests/OpenClaw.Tray.Tests/AsyncListLoadingPageWiringTests.cs b/tests/OpenClaw.Tray.Tests/AsyncListLoadingPageWiringTests.cs index 13bc3d647..29aefe39c 100644 --- a/tests/OpenClaw.Tray.Tests/AsyncListLoadingPageWiringTests.cs +++ b/tests/OpenClaw.Tray.Tests/AsyncListLoadingPageWiringTests.cs @@ -41,7 +41,8 @@ public void BigListPages_SurfaceDisconnectedState(string fileName) var source = ReadSource("src", "OpenClaw.Tray.WinUI", "Pages", fileName); Assert.Contains("ShowDisconnected", source); - Assert.Contains("Navigate(\"connection\")", source); + // Match both Navigate("connection") and Navigate("connection", ""). + Assert.Contains("Navigate(\"connection\"", source); Assert.Contains(".Fail()", source); }