From 988003513e0cb55bafce193d9a95d3d0b0d74ca7 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Mon, 6 Apr 2026 15:15:06 +0800 Subject: [PATCH] Fix gateway target visibility after settings apply --- lib/app/app_controller_desktop_settings.dart | 42 ++++++++++++------- .../settings/settings_page_support.dart | 11 +++-- test/features/settings_page_suite.dart | 16 +++++++ ...sktop_refactor_characterization_suite.dart | 25 +++++++++++ 4 files changed, 76 insertions(+), 18 deletions(-) diff --git a/lib/app/app_controller_desktop_settings.dart b/lib/app/app_controller_desktop_settings.dart index bc4ab235..5a90fa92 100644 --- a/lib/app/app_controller_desktop_settings.dart +++ b/lib/app/app_controller_desktop_settings.dart @@ -48,6 +48,26 @@ import 'app_controller_desktop_runtime_helpers.dart'; // ignore_for_file: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member extension AppControllerDesktopSettings on AppController { + SettingsSnapshot _markSavedGatewayTargetsForChangedProfiles( + SettingsSnapshot previous, + SettingsSnapshot snapshot, + ) { + var nextSnapshot = snapshot; + if (jsonEncode(previous.primaryLocalGatewayProfile.toJson()) != + jsonEncode(snapshot.primaryLocalGatewayProfile.toJson())) { + nextSnapshot = nextSnapshot.markGatewayTargetSaved( + AssistantExecutionTarget.local, + ); + } + if (jsonEncode(previous.primaryRemoteGatewayProfile.toJson()) != + jsonEncode(snapshot.primaryRemoteGatewayProfile.toJson())) { + nextSnapshot = nextSnapshot.markGatewayTargetSaved( + AssistantExecutionTarget.remote, + ); + } + return nextSnapshot; + } + Future saveSettingsDraft(SettingsSnapshot snapshot) async { if (disposedInternal) { return; @@ -173,7 +193,10 @@ extension AppControllerDesktopSettings on AppController { notifyListeners(); return; } - final nextSettings = settingsDraft; + final nextSettings = _markSavedGatewayTargetsForChangedProfiles( + settings, + settingsDraft, + ); markPendingApplyDomainsInternal(settings, nextSettings); await persistDraftSecretsInternal(); if (nextSettings.toJsonString() != settings.toJsonString()) { @@ -237,19 +260,10 @@ extension AppControllerDesktopSettings on AppController { return; } final previous = settings; - var nextSnapshot = snapshot; - if (jsonEncode(previous.primaryLocalGatewayProfile.toJson()) != - jsonEncode(snapshot.primaryLocalGatewayProfile.toJson())) { - nextSnapshot = nextSnapshot.markGatewayTargetSaved( - AssistantExecutionTarget.local, - ); - } - if (jsonEncode(previous.primaryRemoteGatewayProfile.toJson()) != - jsonEncode(snapshot.primaryRemoteGatewayProfile.toJson())) { - nextSnapshot = nextSnapshot.markGatewayTargetSaved( - AssistantExecutionTarget.remote, - ); - } + final nextSnapshot = _markSavedGatewayTargetsForChangedProfiles( + previous, + snapshot, + ); await persistSettingsSnapshotInternal(nextSnapshot); if (disposedInternal) { return; diff --git a/lib/features/settings/settings_page_support.dart b/lib/features/settings/settings_page_support.dart index 01e254ce..9a16ba78 100644 --- a/lib/features/settings/settings_page_support.dart +++ b/lib/features/settings/settings_page_support.dart @@ -556,10 +556,13 @@ XWorkmate Privacy Policy SettingsSnapshot settings, GatewayConnectionProfile profile, ) async { - final nextSettings = settings.copyWithGatewayProfileAt( - selectedGatewayProfileIndexInternal, - profile, - ); + final executionTarget = + selectedGatewayProfileIndexInternal == kGatewayLocalProfileIndex + ? AssistantExecutionTarget.local + : AssistantExecutionTarget.remote; + final nextSettings = settings + .copyWithGatewayProfileAt(selectedGatewayProfileIndexInternal, profile) + .markGatewayTargetSaved(executionTarget); await saveSettingsInternal(controller, nextSettings); if (!mounted) { return; diff --git a/test/features/settings_page_suite.dart b/test/features/settings_page_suite.dart index 3c1605be..a0852607 100644 --- a/test/features/settings_page_suite.dart +++ b/test/features/settings_page_suite.dart @@ -767,6 +767,22 @@ paths: expect(find.byKey(const ValueKey('gateway-host-field')), findsOneWidget); }); + testWidgets( + 'SettingsPage gateway save and apply marks the selected gateway target as saved even for default-valued profiles', + (WidgetTester tester) async { + final controller = await createTestController(tester); + + await _pumpSettingsPage(tester, controller, tab: SettingsTab.gateway); + await tester.tap(find.byKey(const ValueKey('gateway-profile-chip-0'))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const ValueKey('gateway-apply-button'))); + await tester.pumpAndSettle(); + + expect(controller.settings.savedGatewayTargets, contains('local')); + }, + ); + testWidgets('SettingsPage diagnostics tab filters and clears runtime logs', ( WidgetTester tester, ) async { diff --git a/test/runtime/app_controller_desktop_refactor_characterization_suite.dart b/test/runtime/app_controller_desktop_refactor_characterization_suite.dart index 8d0e2e34..a2a963ac 100644 --- a/test/runtime/app_controller_desktop_refactor_characterization_suite.dart +++ b/test/runtime/app_controller_desktop_refactor_characterization_suite.dart @@ -97,6 +97,31 @@ void main() { }, ); + test( + 'AppController marks gateway targets as saved when settings drafts are applied', + () async { + final harness = await _DesktopControllerHarness.create(); + addTearDown(harness.dispose); + final controller = harness.controller; + final defaults = controller.settings; + final nextSettings = defaults.copyWith( + gatewayProfiles: replaceGatewayProfileAt( + defaults.gatewayProfiles, + kGatewayLocalProfileIndex, + defaults.primaryLocalGatewayProfile.copyWith( + host: '127.0.0.1', + port: 18789, + ), + ), + ); + + await controller.saveSettingsDraft(nextSettings); + await controller.applySettingsDraft(); + + expect(controller.settings.savedGatewayTargets, contains('local')); + }, + ); + test( 'AppController keeps AI Gateway model choices when single-agent falls back to AI chat', () async {