You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by zh...@apache.org on 2026/09/01 15:07:26 UTC

(maka) 03/10: refactor(desktop): move connection settings behind feature adapter

This is an automated email from the ASF dual-hosted git repository.

me2seeks pushed a commit to branch feat/desktop-api-key-host-onboarding
in repository https://gitbox.apache.org/repos/asf/maka.git

commit de03a7d024e49affca8022a8d684dd08c12d3f0a
Author: me2seeks <me...@gmail.com>
AuthorDate: Tue Sep 1 01:02:00 2026 +0800

    refactor(desktop): move connection settings behind feature adapter
---
 .../e2e/request-header-row-contract.spec.ts        |   2 +-
 apps/desktop/renderer-architecture.json            |  78 +++-----------
 .../main/__tests__/provider-add-submission.test.ts |  63 +++++++++++-
 .../contracts/connection-error-cleaner.ts          |  37 +++++++
 .../composition/desktop-feature-services.tsx       |  13 ++-
 .../renderer/features/connection-settings/index.ts |  44 ++++++++
 .../renderer/features/connection-settings/ports.ts |  92 +++++++++++++++++
 .../connection-settings}/provider-panel-shared.ts  | 112 +--------------------
 .../connection-settings/services-context.tsx       |  46 +++++++++
 .../connection-settings}/settings-provider-copy.ts |   0
 .../src/renderer/model-connection-errors.ts        |  31 ++----
 .../desktop/create-connection-settings-services.ts |  69 +++++++++++++
 .../renderer/settings/general-settings-page.tsx    |   2 +-
 .../src/renderer/settings/provider-add-form.tsx    |   4 +-
 .../settings/provider-add-model-dialog.tsx         |   2 +-
 .../renderer/settings/provider-catalog-page.tsx    |   4 +-
 .../settings/provider-connection-detail.tsx        |   4 +-
 .../settings/provider-connection-status.ts         |   2 +-
 .../settings/provider-enabled-model-manager.tsx    |   2 +-
 .../renderer/settings/provider-oauth-section.tsx   |   2 +-
 .../src/renderer/settings/providers-panel.tsx      |   6 +-
 .../settings/runtime-host-settings-bridge.ts       |  60 -----------
 .../src/renderer/settings/settings-surface.tsx     |  47 ++++++---
 .../src/renderer/settings/use-connection-detail.ts |   4 +-
 .../src/renderer/settings/use-oauth-login-flow.ts  |   2 +-
 .../stories/settings/provider-settings.stories.tsx |   2 +-
 .../stories/settings/settings-pages.stories.tsx    |  49 +++++----
 27 files changed, 457 insertions(+), 322 deletions(-)

diff --git a/apps/desktop/e2e/request-header-row-contract.spec.ts b/apps/desktop/e2e/request-header-row-contract.spec.ts
index 743afe201d..c820b17743 100644
--- a/apps/desktop/e2e/request-header-row-contract.spec.ts
+++ b/apps/desktop/e2e/request-header-row-contract.spec.ts
@@ -18,7 +18,7 @@
  */
 
 import { expect, test } from './fixtures';
-import { getProviderSettingsCopy } from '../src/renderer/locales/settings-provider-copy';
+import { getProviderSettingsCopy } from '../src/renderer/features/connection-settings';
 
 /**
  * The custom request header row's remove button centres on the FIELD, not on
diff --git a/apps/desktop/renderer-architecture.json b/apps/desktop/renderer-architecture.json
index d74d340707..a6f5266c52 100644
--- a/apps/desktop/renderer-architecture.json
+++ b/apps/desktop/renderer-architecture.json
@@ -80,7 +80,6 @@
     "src/renderer/locales/settings-navigation-copy.ts",
     "src/renderer/locales/settings-preferences-copy.ts",
     "src/renderer/locales/settings-projects-copy.ts",
-    "src/renderer/locales/settings-provider-copy.ts",
     "src/renderer/locales/settings-shared-copy.ts",
     "src/renderer/locales/settings-subagents-copy.ts",
     "src/renderer/locales/settings-tasks-copy.ts",
@@ -169,7 +168,6 @@
     "src/renderer/settings/provider-enabled-model-manager.tsx",
     "src/renderer/settings/provider-endpoint-presentation.ts",
     "src/renderer/settings/provider-oauth-section.tsx",
-    "src/renderer/settings/provider-panel-shared.ts",
     "src/renderer/settings/providers-panel.tsx",
     "src/renderer/settings/relay-profile-draft.ts",
     "src/renderer/settings/relay-thinking-bulk.ts",
@@ -1968,17 +1966,6 @@
           "@maka/runtime-host/operator": 1
         }
       },
-      "src/renderer/locales/settings-provider-copy.ts": {
-        "bridgePaths": {},
-        "environmentCapabilities": {},
-        "hookCalls": {},
-        "lifecycleMethods": {},
-        "unresolvedDependencies": 0,
-        "actionFactories": [],
-        "dependencyPaths": {
-          "@maka/core/ui-locale": 1
-        }
-      },
       "src/renderer/locales/settings-shared-copy.ts": {
         "bridgePaths": {},
         "environmentCapabilities": {},
@@ -2220,10 +2207,10 @@
         "unresolvedDependencies": 0,
         "actionFactories": [],
         "dependencyPaths": {
+          "./application/contracts/connection-error-cleaner.js": 1,
           "./locales/conversation-copy.js": 1,
           "./locales/shell-copy.js": 1,
           "./session-error-presentation.js": 1,
-          "@maka/core/connection-error-copy": 1,
           "@maka/core/connection-readiness": 1,
           "@maka/core/events": 1,
           "@maka/core/ui-locale": 1
@@ -3079,6 +3066,7 @@
         "unresolvedDependencies": 0,
         "actionFactories": [],
         "dependencyPaths": {
+          "../features/connection-settings": 1,
           "../locales/settings-preferences-copy.js": 1,
           "../locales/settings-shared-copy.js": 1,
           "../locales/settings-test-result-copy.js": 1,
@@ -3086,7 +3074,6 @@
           "./password-input": 1,
           "./personalization-settings-section": 1,
           "./provider-brand-marks": 1,
-          "./runtime-host-settings-bridge.js": 1,
           "./runtime-host-settings-target.js": 1,
           "./settings-error-copy": 1,
           "./settings-resource-state.js": 1,
@@ -3447,12 +3434,11 @@
         "unresolvedDependencies": 0,
         "actionFactories": [],
         "dependencyPaths": {
-          "../locales/settings-provider-copy": 1,
+          "../features/connection-settings": 1,
           "../model-catalog-choices": 1,
           "./password-input": 1,
           "./provider-add-submission": 1,
           "./provider-display": 1,
-          "./provider-panel-shared": 1,
           "./request-customization-editor": 1,
           "./use-action-guard": 1,
           "@astryxdesign/core": 1,
@@ -3473,7 +3459,7 @@
         "unresolvedDependencies": 0,
         "actionFactories": [],
         "dependencyPaths": {
-          "../locales/settings-provider-copy": 1,
+          "../features/connection-settings": 1,
           "@astryxdesign/core/Dialog": 1,
           "@astryxdesign/core/FormLayout": 1,
           "@astryxdesign/core/Layout": 1,
@@ -3540,11 +3526,10 @@
         "unresolvedDependencies": 0,
         "actionFactories": [],
         "dependencyPaths": {
-          "../locales/settings-provider-copy": 1,
+          "../features/connection-settings": 1,
           "./provider-add-form": 1,
           "./provider-display": 1,
           "./provider-oauth-section": 1,
-          "./provider-panel-shared": 1,
           "@astryxdesign/core": 1,
           "@maka/core/llm-connections": 1,
           "@maka/core/provider-registry": 1,
@@ -3573,13 +3558,12 @@
         "unresolvedDependencies": 0,
         "actionFactories": [],
         "dependencyPaths": {
-          "../locales/settings-provider-copy": 1,
+          "../features/connection-settings": 1,
           "./password-input": 1,
           "./provider-add-model-dialog": 1,
           "./provider-display": 1,
           "./provider-enabled-model-manager": 1,
           "./provider-endpoint-presentation": 1,
-          "./provider-panel-shared": 1,
           "./relay-thinking-bulk": 1,
           "./request-customization-editor": 1,
           "./runtime-host-settings-target.js": 1,
@@ -3604,7 +3588,7 @@
         "unresolvedDependencies": 0,
         "actionFactories": [],
         "dependencyPaths": {
-          "../locales/settings-provider-copy.js": 1,
+          "../features/connection-settings": 1,
           "@maka/core/llm-connections": 1,
           "@maka/core/provider-registry": 1,
           "@maka/core/ui-locale": 1,
@@ -3646,7 +3630,7 @@
         "unresolvedDependencies": 0,
         "actionFactories": [],
         "dependencyPaths": {
-          "../locales/settings-provider-copy": 1,
+          "../features/connection-settings": 1,
           "@astryxdesign/core": 1,
           "@maka/core/model-catalog": 1,
           "@maka/ui": 1
@@ -3690,7 +3674,7 @@
         "actionFactories": [],
         "dependencyPaths": {
           "../../preload/bridge-contract.js": 1,
-          "../locales/settings-provider-copy": 1,
+          "../features/connection-settings": 1,
           "./runtime-host-settings-bridge.js": 1,
           "./runtime-host-settings-target.js": 1,
           "./use-oauth-login-flow": 1,
@@ -3700,22 +3684,6 @@
           "react": 1
         }
       },
-      "src/renderer/settings/provider-panel-shared.ts": {
-        "bridgePaths": {},
-        "environmentCapabilities": {},
-        "hookCalls": {},
-        "lifecycleMethods": {},
-        "unresolvedDependencies": 0,
-        "actionFactories": [],
-        "dependencyPaths": {
-          "../../shared/desktop-connection-snapshot.js": 1,
-          "../locales/settings-provider-copy.js": 1,
-          "../model-connection-errors.js": 1,
-          "@maka/core/llm-connections": 1,
-          "@maka/core/redaction": 1,
-          "@maka/core/ui-locale": 1
-        }
-      },
       "src/renderer/settings/providers-panel.tsx": {
         "bridgePaths": {},
         "environmentCapabilities": {
@@ -3734,13 +3702,12 @@
         "unresolvedDependencies": 0,
         "actionFactories": [],
         "dependencyPaths": {
-          "../locales/settings-provider-copy": 1,
+          "../features/connection-settings": 2,
           "./provider-catalog-page": 1,
           "./provider-connection-detail": 1,
           "./provider-connection-status": 1,
           "./provider-display": 1,
           "./provider-oauth-section": 1,
-          "./provider-panel-shared": 2,
           "./runtime-host-settings-target.js": 1,
           "./settings-error-copy": 1,
           "./settings-route-focus": 1,
@@ -3965,22 +3932,7 @@
         }
       },
       "src/renderer/settings/runtime-host-settings-bridge.ts": {
-        "bridgePaths": {
-          "window.maka.connections.create": 1,
-          "window.maka.connections.delete": 1,
-          "window.maka.connections.fetchModels": 1,
-          "window.maka.connections.getRequestHeaders": 1,
-          "window.maka.connections.getSnapshot": 1,
-          "window.maka.connections.hasSecret": 1,
-          "window.maka.connections.saveOnboarding": 1,
-          "window.maka.connections.setDefault": 1,
-          "window.maka.connections.setDefaultModel": 1,
-          "window.maka.connections.setRequestHeaders": 1,
-          "window.maka.connections.subscribeEvents": 1,
-          "window.maka.connections.test": 1,
-          "window.maka.connections.update": 1,
-          "window.maka.connections.verifyOnboarding": 1
-        },
+        "bridgePaths": {},
         "environmentCapabilities": {},
         "hookCalls": {},
         "lifecycleMethods": {},
@@ -3988,7 +3940,6 @@
         "actionFactories": [],
         "dependencyPaths": {
           "../../preload/bridge-contract.js": 1,
-          "./provider-panel-shared.js": 1,
           "./use-oauth-login-flow.js": 1
         }
       },
@@ -4280,6 +4231,7 @@
           "../../preload/bridge-contract.js": 1,
           "../../shared/settings-ownership.js": 1,
           "../browser-storage": 1,
+          "../features/connection-settings": 1,
           "../locales/settings-navigation-copy.js": 1,
           "../locales/settings-shared-copy.js": 1,
           "./about-settings-page": 1,
@@ -4295,7 +4247,6 @@
           "./projects-settings-page": 1,
           "./providers-panel": 1,
           "./runtime-host-interaction-boundary.js": 1,
-          "./runtime-host-settings-bridge.js": 1,
           "./runtime-host-settings-target.js": 1,
           "./settings-error-copy": 1,
           "./settings-nav": 1,
@@ -4494,10 +4445,9 @@
         "actionFactories": [],
         "dependencyPaths": {
           "../../preload/bridge-contract.js": 1,
-          "../locales/settings-provider-copy": 1,
+          "../features/connection-settings": 1,
           "./connection-name-draft.js": 1,
           "./provider-connection-status": 1,
-          "./provider-panel-shared": 1,
           "./relay-profile-draft": 1,
           "./relay-thinking-bulk": 1,
           "./runtime-host-settings-bridge.js": 1,
@@ -4576,7 +4526,7 @@
         "unresolvedDependencies": 0,
         "actionFactories": [],
         "dependencyPaths": {
-          "../locales/settings-provider-copy": 1,
+          "../features/connection-settings": 1,
           "./oauth-login-flow-guard": 1,
           "./runtime-host-settings-target.js": 1,
           "@maka/core/redaction": 1,
diff --git a/apps/desktop/src/main/__tests__/provider-add-submission.test.ts b/apps/desktop/src/main/__tests__/provider-add-submission.test.ts
index 05e1689cc2..7abf64b8ae 100644
--- a/apps/desktop/src/main/__tests__/provider-add-submission.test.ts
+++ b/apps/desktop/src/main/__tests__/provider-add-submission.test.ts
@@ -29,7 +29,7 @@ import {
   type AddProviderDraft,
   type AddProviderField,
 } from '../../renderer/settings/provider-add-submission.js';
-import { runtimeHostApiKeyOnboardingBridge } from '../../renderer/settings/runtime-host-settings-bridge.js';
+import { createDesktopConnectionSettingsServices } from '../../renderer/platform/desktop/create-connection-settings-services.js';
 import {
   PROVIDER_REGISTRY,
   providerSupportsModelDiscovery,
@@ -277,17 +277,22 @@ test('a remounted setup becomes uncertain only after save dispatch', () => {
 });
 
 test('a late old save cannot clear the uncertainty lease for a newer attempt', () => {
-  const firstView = runtimeHostApiKeyOnboardingBridge({
+  const services = createDesktopConnectionSettingsServices(() => ({
+    // This test exercises only the in-process attempt lease. Transport calls
+    // remain behind the adapter and are intentionally unavailable here.
+    connections: {},
+  } as unknown as ReturnType<NonNullable<Parameters<typeof createDesktopConnectionSettingsServices>[0]>>));
+  const firstView = services.forHost({
     profileId: 'local',
     hostId: 'same-host',
-  });
+  }).apiKeyOnboarding;
   const oldAttempt = firstView.saveUncertainty.markDispatched();
   firstView.saveUncertainty.restart();
 
-  const replacementView = runtimeHostApiKeyOnboardingBridge({
+  const replacementView = services.forHost({
     profileId: 'local',
     hostId: 'same-host',
-  });
+  }).apiKeyOnboarding;
   const newAttempt = replacementView.saveUncertainty.markDispatched();
   firstView.saveUncertainty.settle(oldAttempt);
   assert.equal(replacementView.saveUncertainty.isUncertain(), true);
@@ -295,3 +300,51 @@ test('a late old save cannot clear the uncertainty lease for a newer attempt', (
   replacementView.saveUncertainty.settle(newAttempt);
   assert.equal(firstView.saveUncertainty.isUncertain(), false);
 });
+
+test('save uncertainty is isolated between Runtime Host targets', () => {
+  const services = createDesktopConnectionSettingsServices(() => ({
+    connections: {},
+  } as unknown as ReturnType<NonNullable<Parameters<typeof createDesktopConnectionSettingsServices>[0]>>));
+  const firstHost = services.forHost({ profileId: 'local', hostId: 'host-a' }).apiKeyOnboarding;
+  const secondHost = services.forHost({ profileId: 'local', hostId: 'host-b' }).apiKeyOnboarding;
+
+  firstHost.saveUncertainty.markDispatched();
+
+  assert.equal(firstHost.saveUncertainty.isUncertain(), true);
+  assert.equal(secondHost.saveUncertainty.isUncertain(), false);
+});
+
+test('onboarding transport forwards the selected Runtime Host target', async () => {
+  const calls: unknown[] = [];
+  const services = createDesktopConnectionSettingsServices(() => ({
+    connections: {
+      verifyOnboarding: async (_input: unknown, host: unknown) => {
+        calls.push(['verify', host]);
+        return { kind: 'rejected', reason: 'provider_unsupported' } as const;
+      },
+      saveOnboarding: async (_input: unknown, host: unknown) => {
+        calls.push(['save', host]);
+        return { kind: 'rejected', reason: 'provider_unsupported' } as const;
+      },
+    },
+  } as unknown as ReturnType<NonNullable<Parameters<typeof createDesktopConnectionSettingsServices>[0]>>));
+  const host = { profileId: 'remote', hostId: 'host-remote' };
+  const onboarding = services.forHost(host).apiKeyOnboarding;
+
+  await onboarding.verify({
+    target: { kind: 'create', providerType: 'openai' },
+    apiKey: 'test-key',
+    baseUrl: null,
+  });
+  await onboarding.save({
+    target: { kind: 'create', providerType: 'openai' },
+    apiKey: 'test-key',
+    baseUrl: null,
+    enabledModelIds: ['gpt-5'],
+  });
+
+  assert.deepEqual(calls, [
+    ['verify', host],
+    ['save', host],
+  ]);
+});
diff --git a/apps/desktop/src/renderer/application/contracts/connection-error-cleaner.ts b/apps/desktop/src/renderer/application/contracts/connection-error-cleaner.ts
new file mode 100644
index 0000000000..05956ede6d
--- /dev/null
+++ b/apps/desktop/src/renderer/application/contracts/connection-error-cleaner.ts
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { parseNoRealConnectionError } from '@maka/core/connection-error-copy';
+
+export { parseNoRealConnectionError };
+
+export const NO_REAL_CONNECTION_CODE = 'NO_REAL_CONNECTION';
+const NO_REAL_CONNECTION_REASON_RE = /NO_REAL_CONNECTION:([a-z_]+): /;
+
+export function cleanErrorMessage(error: unknown): string {
+  const raw = error instanceof Error ? error.message : String(error);
+  return cleanEventMessage(raw);
+}
+
+export function cleanEventMessage(message: string): string {
+  return message
+    .replace(/^Error invoking remote method '[^']+': (?:[A-Za-z_$][\w$]*)?Error: /, '')
+    .replace(NO_REAL_CONNECTION_REASON_RE, '')
+    .replace(`${NO_REAL_CONNECTION_CODE}: `, '');
+}
diff --git a/apps/desktop/src/renderer/composition/desktop-feature-services.tsx b/apps/desktop/src/renderer/composition/desktop-feature-services.tsx
index 47c188d6e6..1e968c0880 100644
--- a/apps/desktop/src/renderer/composition/desktop-feature-services.tsx
+++ b/apps/desktop/src/renderer/composition/desktop-feature-services.tsx
@@ -18,6 +18,7 @@
  */
 
 import type { ReactNode } from 'react';
+import { ConnectionSettingsServicesProvider } from '../features/connection-settings';
 import { GoalServicesProvider } from '../features/goals';
 import { ModuleHubServicesProvider } from '../features/module-hub';
 import { RuntimeHostManagementServicesProvider } from '../features/runtime-host-management';
@@ -26,6 +27,7 @@ import { SessionNavigationServicesProvider } from '../features/session-navigatio
 import { TaskEntryServicesProvider } from '../features/task-entry';
 import { WorkbarServicesProvider } from '../features/workbar';
 import { createDesktopGoalServices } from '../platform/desktop/create-goal-services';
+import { createDesktopConnectionSettingsServices } from '../platform/desktop/create-connection-settings-services';
 import { createDesktopModuleHubServices } from '../platform/desktop/create-module-hub-services';
 import { createDesktopRuntimeHostManagementServices } from '../platform/desktop/create-runtime-host-management-services';
 import { createDesktopSessionCollaborationServices } from '../platform/desktop/create-session-collaboration-services';
@@ -35,6 +37,7 @@ import { createDesktopWorkbarServices } from '../platform/desktop/create-workbar
 
 export function createDesktopFeatureServices() {
   return {
+    connectionSettings: createDesktopConnectionSettingsServices(),
     goal: createDesktopGoalServices(),
     moduleHub: createDesktopModuleHubServices(),
     runtimeHostManagement: createDesktopRuntimeHostManagementServices(),
@@ -50,8 +53,9 @@ export function DesktopFeatureServicesProvider(props: {
   readonly children?: ReactNode;
 }) {
   return (
-    <RuntimeHostManagementServicesProvider services={props.services.runtimeHostManagement}>
-      <SessionCollaborationServicesProvider services={props.services.sessionCollaboration}>
+    <ConnectionSettingsServicesProvider services={props.services.connectionSettings}>
+      <RuntimeHostManagementServicesProvider services={props.services.runtimeHostManagement}>
+        <SessionCollaborationServicesProvider services={props.services.sessionCollaboration}>
         <SessionNavigationServicesProvider services={props.services.sessionNavigation}>
           <TaskEntryServicesProvider services={props.services.taskEntry}>
             <ModuleHubServicesProvider services={props.services.moduleHub}>
@@ -63,7 +67,8 @@ export function DesktopFeatureServicesProvider(props: {
             </ModuleHubServicesProvider>
           </TaskEntryServicesProvider>
         </SessionNavigationServicesProvider>
-      </SessionCollaborationServicesProvider>
-    </RuntimeHostManagementServicesProvider>
+        </SessionCollaborationServicesProvider>
+      </RuntimeHostManagementServicesProvider>
+    </ConnectionSettingsServicesProvider>
   );
 }
diff --git a/apps/desktop/src/renderer/features/connection-settings/index.ts b/apps/desktop/src/renderer/features/connection-settings/index.ts
new file mode 100644
index 0000000000..d58839002d
--- /dev/null
+++ b/apps/desktop/src/renderer/features/connection-settings/index.ts
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export {
+  ConnectionSettingsServicesProvider,
+  ConnectionSettingsServicesConsumer,
+} from './services-context';
+export type {
+  ApiKeyOnboardingBridge,
+  ConnectionSettingsServices,
+  ConnectionSettingsHost,
+  ConnectionsBridge,
+  DesktopConnectionOnboardingIdentity,
+  RuntimeHostSettingsConnectionsBridge,
+} from './ports';
+export {
+  categoryLabel,
+  connectionLastTestMessageDisplay,
+  connectionTestFailureFallback,
+  connectionTestFailureMessage,
+  providerPanelActionErrorMessage,
+} from './provider-panel-shared';
+export { getProviderSettingsCopy } from './settings-provider-copy';
+export type { ProviderSettingsCopy } from './settings-provider-copy';
+export type {
+  ConnectionTestTroubleshootingCopy,
+  CredentialPresenceStatus,
+} from './provider-panel-shared';
diff --git a/apps/desktop/src/renderer/features/connection-settings/ports.ts b/apps/desktop/src/renderer/features/connection-settings/ports.ts
new file mode 100644
index 0000000000..bb107c4dc4
--- /dev/null
+++ b/apps/desktop/src/renderer/features/connection-settings/ports.ts
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type {
+  ConnectionTestResult,
+  CreateConnectionInput,
+  IdentifiedLlmConnection,
+  LlmConnection,
+  ModelDiscoveryResult,
+  RequestHeaderUpdate,
+  SavedRequestHeaders,
+  UpdateConnectionInput,
+} from '@maka/core/llm-connections';
+import type {
+  ConnectionOnboardingSaveInput,
+  ConnectionOnboardingSaveResult,
+  ConnectionOnboardingVerifyInput,
+  ConnectionOnboardingVerifyResult,
+} from '@maka/runtime-host/protocol';
+import type {
+  DesktopConnectionIdentity,
+  DesktopConnectionSnapshot,
+} from '../../../shared/desktop-connection-snapshot.js';
+export interface ConnectionSettingsHost {
+  readonly profileId: string;
+  readonly hostId: string;
+}
+
+export type DesktopConnectionOnboardingIdentity = Extract<
+  ConnectionOnboardingSaveResult,
+  { readonly kind: 'saved' }
+>['connection'];
+
+export interface ConnectionsBridge {
+  getSnapshot(): Promise<DesktopConnectionSnapshot>;
+  setDefault(connection: DesktopConnectionIdentity | null): Promise<void>;
+  create(input: CreateConnectionInput): Promise<IdentifiedLlmConnection>;
+  update(connection: DesktopConnectionIdentity, patch: UpdateConnectionInput): Promise<LlmConnection>;
+  delete(connection: DesktopConnectionIdentity): Promise<void>;
+  test(connection: DesktopConnectionIdentity, opts?: { model?: string }): Promise<ConnectionTestResult>;
+  fetchModels(connection: DesktopConnectionIdentity): Promise<ModelDiscoveryResult>;
+  hasSecret(connection: DesktopConnectionIdentity): Promise<boolean>;
+  getRequestHeaders(connection: DesktopConnectionIdentity): Promise<SavedRequestHeaders>;
+  setRequestHeaders(
+    connection: DesktopConnectionIdentity,
+    headers: readonly RequestHeaderUpdate[],
+  ): Promise<SavedRequestHeaders>;
+  subscribeEvents?(handler: () => void): () => void;
+}
+
+export interface RuntimeHostSettingsConnectionsBridge extends ConnectionsBridge {
+  setDefaultModel(input: { slug: string; model: string } | null): Promise<void>;
+  subscribeEvents(handler: () => void): () => void;
+}
+
+export interface ApiKeyOnboardingBridge {
+  readonly saveUncertainty: {
+    isUncertain(): boolean;
+    markDispatched(): number;
+    settle(attemptId: number): void;
+    restart(): void;
+  };
+  verify(input: ConnectionOnboardingVerifyInput): Promise<ConnectionOnboardingVerifyResult>;
+  save(input: ConnectionOnboardingSaveInput): Promise<
+    | { readonly kind: 'result'; readonly result: ConnectionOnboardingSaveResult }
+    | { readonly kind: 'not_saved' }
+    | { readonly kind: 'outcome_unknown' }
+  >;
+}
+
+export interface ConnectionSettingsServices {
+  forHost(host: ConnectionSettingsHost): {
+    readonly connections: RuntimeHostSettingsConnectionsBridge;
+    readonly apiKeyOnboarding: ApiKeyOnboardingBridge;
+  };
+}
diff --git a/apps/desktop/src/renderer/settings/provider-panel-shared.ts b/apps/desktop/src/renderer/features/connection-settings/provider-panel-shared.ts
similarity index 55%
rename from apps/desktop/src/renderer/settings/provider-panel-shared.ts
rename to apps/desktop/src/renderer/features/connection-settings/provider-panel-shared.ts
index 0a72af26b8..92dc3a9231 100644
--- a/apps/desktop/src/renderer/settings/provider-panel-shared.ts
+++ b/apps/desktop/src/renderer/features/connection-settings/provider-panel-shared.ts
@@ -20,119 +20,11 @@
 import { generalizedErrorMessage, generalizedErrorMessageChinese, redactSecrets } from '@maka/core/redaction';
 import {
   type ConnectionTestResult,
-  type CreateConnectionInput,
-  type IdentifiedLlmConnection,
-  type LlmConnection,
-  type ModelDiscoveryResult,
-  type ModelInfo,
-  type RequestHeaderUpdate,
-  type SavedRequestHeaders,
   type ProviderCategory,
-  type ProviderType,
-  type UpdateConnectionInput,
 } from '@maka/core/llm-connections';
 import { type UiLocale } from '@maka/core/ui-locale';
-import { getProviderSettingsCopy } from '../locales/settings-provider-copy.js';
-import { cleanErrorMessage } from '../model-connection-errors.js';
-import type {
-  DesktopConnectionIdentity,
-  DesktopConnectionSnapshot,
-} from '../../shared/desktop-connection-snapshot.js';
-
-export type DesktopConnectionOnboardingIdentity = {
-  readonly connectionId: string;
-  readonly revision: number;
-  readonly slug: string;
-  readonly providerType: ProviderType;
-};
-
-export type ApiKeyOnboardingTarget =
-  | { readonly kind: 'create'; readonly providerType: ProviderType }
-  | { readonly kind: 'existing'; readonly connectionId: string };
-
-export type ApiKeyOnboardingVerifyResult =
-  | { readonly kind: 'verified'; readonly models: readonly ModelInfo[] }
-  | {
-      readonly kind: 'rejected';
-      readonly reason:
-        | 'provider_unsupported'
-        | 'connection_not_found'
-        | 'credential_not_configured'
-        | 'base_url_not_configured'
-        | 'catalog_full';
-    }
-  | {
-      readonly kind: 'failed';
-      readonly errorClass:
-        | 'auth'
-        | 'timeout'
-        | 'provider_unavailable'
-        | 'network'
-        | 'invalid_response'
-        | 'unknown';
-    };
-
-export type ApiKeyOnboardingSaveResult =
-  | { readonly kind: 'saved'; readonly connection: DesktopConnectionOnboardingIdentity }
-  | {
-      readonly kind: 'rejected';
-      readonly reason:
-        | 'provider_unsupported'
-        | 'connection_not_found'
-        | 'credential_not_configured'
-        | 'base_url_not_configured'
-        | 'catalog_full'
-        | 'model_unavailable'
-        | 'superseded';
-    }
-  | Exclude<ApiKeyOnboardingVerifyResult, { readonly kind: 'verified' | 'rejected' }>;
-
-export interface ApiKeyOnboardingBridge {
-  readonly saveUncertainty: {
-    isUncertain(): boolean;
-    markDispatched(): number;
-    settle(attemptId: number): void;
-    restart(): void;
-  };
-  verify(input: {
-    readonly target: ApiKeyOnboardingTarget;
-    readonly apiKey: string | null;
-    readonly baseUrl: string | null;
-  }): Promise<ApiKeyOnboardingVerifyResult>;
-  save(input: {
-    readonly target: ApiKeyOnboardingTarget;
-    readonly apiKey: string | null;
-    readonly baseUrl: string | null;
-    readonly enabledModelIds: readonly string[];
-  }): Promise<
-    | { readonly kind: 'result'; readonly result: ApiKeyOnboardingSaveResult }
-    | { readonly kind: 'not_saved' }
-    | { readonly kind: 'outcome_unknown' }
-  >;
-}
-
-export interface ConnectionsBridge {
-  getSnapshot(): Promise<DesktopConnectionSnapshot>;
-  setDefault(connection: DesktopConnectionIdentity | null): Promise<void>;
-  create(input: CreateConnectionInput): Promise<IdentifiedLlmConnection>;
-  update(connection: DesktopConnectionIdentity, patch: UpdateConnectionInput): Promise<LlmConnection>;
-  delete(connection: DesktopConnectionIdentity): Promise<void>;
-  test(connection: DesktopConnectionIdentity, opts?: { model?: string }): Promise<ConnectionTestResult>;
-  /**
-   * What the discovery run found, minus when it ran: the Host records that
-   * timestamp for its own invalidation, and no surface here shows it.
-   */
-  fetchModels(
-    connection: DesktopConnectionIdentity,
-  ): Promise<Pick<ModelDiscoveryResult, 'models' | 'source'>>;
-  hasSecret(connection: DesktopConnectionIdentity): Promise<boolean>;
-  getRequestHeaders(connection: DesktopConnectionIdentity): Promise<SavedRequestHeaders>;
-  setRequestHeaders(
-    connection: DesktopConnectionIdentity,
-    headers: readonly RequestHeaderUpdate[],
-  ): Promise<SavedRequestHeaders>;
-  subscribeEvents?(handler: () => void): () => void;
-}
+import { getProviderSettingsCopy } from './settings-provider-copy.js';
+import { cleanErrorMessage } from '../../application/contracts/connection-error-cleaner.js';
 
 export type CredentialPresenceStatus = boolean | 'loading' | 'error';
 
diff --git a/apps/desktop/src/renderer/features/connection-settings/services-context.tsx b/apps/desktop/src/renderer/features/connection-settings/services-context.tsx
new file mode 100644
index 0000000000..cfa3464f83
--- /dev/null
+++ b/apps/desktop/src/renderer/features/connection-settings/services-context.tsx
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { createContext, useContext, type ReactNode } from 'react';
+import type { ConnectionSettingsServices } from './ports.js';
+
+const ConnectionSettingsServicesContext = createContext<ConnectionSettingsServices | null>(null);
+
+export function ConnectionSettingsServicesProvider(props: {
+  readonly services: ConnectionSettingsServices;
+  readonly children?: ReactNode;
+}) {
+  return (
+    <ConnectionSettingsServicesContext.Provider value={props.services}>
+      {props.children}
+    </ConnectionSettingsServicesContext.Provider>
+  );
+}
+
+export function useConnectionSettingsServices(): ConnectionSettingsServices {
+  const services = useContext(ConnectionSettingsServicesContext);
+  if (!services) throw new Error('ConnectionSettingsServicesProvider is missing');
+  return services;
+}
+
+export function ConnectionSettingsServicesConsumer(props: {
+  readonly children: (services: ConnectionSettingsServices) => ReactNode;
+}) {
+  return props.children(useConnectionSettingsServices());
+}
diff --git a/apps/desktop/src/renderer/locales/settings-provider-copy.ts b/apps/desktop/src/renderer/features/connection-settings/settings-provider-copy.ts
similarity index 100%
rename from apps/desktop/src/renderer/locales/settings-provider-copy.ts
rename to apps/desktop/src/renderer/features/connection-settings/settings-provider-copy.ts
diff --git a/apps/desktop/src/renderer/model-connection-errors.ts b/apps/desktop/src/renderer/model-connection-errors.ts
index ca0c7550d1..529c018ab2 100644
--- a/apps/desktop/src/renderer/model-connection-errors.ts
+++ b/apps/desktop/src/renderer/model-connection-errors.ts
@@ -20,13 +20,17 @@
 import type { ChatConfigurationReason } from '@maka/core/connection-readiness';
 import type { SessionEvent } from '@maka/core/events';
 import type { UiLocale } from '@maka/core/ui-locale';
-import { parseNoRealConnectionError } from '@maka/core/connection-error-copy';
+import {
+  cleanErrorMessage,
+  cleanEventMessage,
+  NO_REAL_CONNECTION_CODE,
+  parseNoRealConnectionError,
+} from './application/contracts/connection-error-cleaner.js';
 import { getDesktopConversationCopy } from './locales/conversation-copy.js';
 import { localizedShellErrorMessage } from './locales/shell-copy.js';
 import { describeSessionErrorReason } from './session-error-presentation.js';
 
-const NO_REAL_CONNECTION_CODE = 'NO_REAL_CONNECTION';
-const NO_REAL_CONNECTION_REASON_RE = /NO_REAL_CONNECTION:([a-z_]+): /;
+export { cleanErrorMessage, cleanEventMessage };
 
 export function isNoRealConnectionError(error: unknown): boolean {
   return parseNoRealConnectionError(error).matched;
@@ -73,27 +77,6 @@ export function sessionEventErrorMessage(
   return localizedShellErrorMessage(new Error(event.message), fallback, locale);
 }
 
-/**
- * Canonical raw-error cleaner: strips the Electron IPC wrapper so error
- * classifiers see the main-process message, not the channel name (which can
- * contain classifier keywords, e.g. the "fetch" in 'connections:fetchModels').
- * Raw output must not reach a toast unclassified — see
- * providerPanelActionErrorMessage for the display-side contract.
- */
-export function cleanErrorMessage(error: unknown): string {
-  const raw = error instanceof Error ? error.message : String(error);
-  return cleanEventMessage(raw);
-}
-
-export function cleanEventMessage(message: string): string {
-  // Electron serializes a rejected handler as `${error.name}: ${error.message}`,
-  // so custom classes arrive as e.g. "ConnectionModelDiscoveryPreconditionError: …".
-  return message
-    .replace(/^Error invoking remote method '[^']+': (?:[A-Za-z_$][\w$]*)?Error: /, '')
-    .replace(NO_REAL_CONNECTION_REASON_RE, '')
-    .replace(`${NO_REAL_CONNECTION_CODE}: `, '');
-}
-
 export function modelSetupToastCopy(
   reason: string | undefined,
   fallback: string,
diff --git a/apps/desktop/src/renderer/platform/desktop/create-connection-settings-services.ts b/apps/desktop/src/renderer/platform/desktop/create-connection-settings-services.ts
new file mode 100644
index 0000000000..f2353febc9
--- /dev/null
+++ b/apps/desktop/src/renderer/platform/desktop/create-connection-settings-services.ts
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { MakaBridge } from '../../../preload/bridge-contract.js';
+import type { ConnectionSettingsServices } from '../../features/connection-settings';
+
+export function createDesktopConnectionSettingsServices(
+  bridge: () => Pick<MakaBridge, 'connections'> = () => window.maka,
+): ConnectionSettingsServices {
+  const uncertainTargets = new Map<string, number>();
+  let nextAttemptId = 1;
+
+  return {
+    forHost: (host) => {
+      const targetKey = `${host.profileId}\u0000${host.hostId}`;
+      return {
+        connections: {
+          getSnapshot: () => bridge().connections.getSnapshot(undefined, host),
+          setDefault: (connection) => bridge().connections.setDefault(connection, host),
+          setDefaultModel: (input) => bridge().connections.setDefaultModel(input, host),
+          create: (input) => bridge().connections.create(input, host),
+          update: (connection, patch) => bridge().connections.update(connection, patch, host),
+          delete: (connection) => bridge().connections.delete(connection, host),
+          test: (connection, options) => bridge().connections.test(connection, options, host),
+          fetchModels: (connection) => bridge().connections.fetchModels(connection, host),
+          hasSecret: (connection) => bridge().connections.hasSecret(connection, host),
+          getRequestHeaders: (connection) => bridge().connections.getRequestHeaders(connection, host),
+          setRequestHeaders: (connection, headers) =>
+            bridge().connections.setRequestHeaders(connection, headers, host),
+          subscribeEvents: (handler) => bridge().connections.subscribeEvents(handler, host),
+        },
+        apiKeyOnboarding: {
+          saveUncertainty: {
+            isUncertain: () => uncertainTargets.has(targetKey),
+            markDispatched: () => {
+              const attemptId = nextAttemptId++;
+              uncertainTargets.set(targetKey, attemptId);
+              return attemptId;
+            },
+            settle: (attemptId) => {
+              if (uncertainTargets.get(targetKey) === attemptId) {
+                uncertainTargets.delete(targetKey);
+              }
+            },
+            restart: () => uncertainTargets.delete(targetKey),
+          },
+          verify: (input) => bridge().connections.verifyOnboarding(input, host),
+          save: (input) => bridge().connections.saveOnboarding(input, host),
+        },
+      };
+    },
+  };
+}
diff --git a/apps/desktop/src/renderer/settings/general-settings-page.tsx b/apps/desktop/src/renderer/settings/general-settings-page.tsx
index 62498c493b..42b0a0dd17 100644
--- a/apps/desktop/src/renderer/settings/general-settings-page.tsx
+++ b/apps/desktop/src/renderer/settings/general-settings-page.tsx
@@ -66,7 +66,7 @@ import { useOptimisticSettingsDraft } from "./use-optimistic-settings-draft";
 import { getSettingsPreferencesCopy } from "../locales/settings-preferences-copy.js";
 import { settingsTestResultMessage } from "../locales/settings-test-result-copy.js";
 import { getShellCopy } from "../locales/shell-copy.js";
-import type { RuntimeHostSettingsConnectionsBridge } from './runtime-host-settings-bridge.js';
+import type { RuntimeHostSettingsConnectionsBridge } from '../features/connection-settings';
 import { getSettingsSharedCopy } from '../locales/settings-shared-copy.js';
 import {
   useOptionalRuntimeHostSettingsTarget,
diff --git a/apps/desktop/src/renderer/settings/provider-add-form.tsx b/apps/desktop/src/renderer/settings/provider-add-form.tsx
index 61086e68bd..f43feaec38 100644
--- a/apps/desktop/src/renderer/settings/provider-add-form.tsx
+++ b/apps/desktop/src/renderer/settings/provider-add-form.tsx
@@ -40,12 +40,12 @@ import { providerDisplay } from './provider-display';
 import { useActionGuard } from './use-action-guard';
 import {
   categoryLabel,
+  getProviderSettingsCopy,
   providerPanelActionErrorMessage,
   type ApiKeyOnboardingBridge,
   type ConnectionsBridge,
   type DesktopConnectionOnboardingIdentity,
-} from './provider-panel-shared';
-import { getProviderSettingsCopy } from '../locales/settings-provider-copy';
+} from '../features/connection-settings';
 import {
   newRequestHeaders,
   parseRequestBodyOverlay,
diff --git a/apps/desktop/src/renderer/settings/provider-add-model-dialog.tsx b/apps/desktop/src/renderer/settings/provider-add-model-dialog.tsx
index 794ee915f1..85ae5c35fc 100644
--- a/apps/desktop/src/renderer/settings/provider-add-model-dialog.tsx
+++ b/apps/desktop/src/renderer/settings/provider-add-model-dialog.tsx
@@ -22,7 +22,7 @@ import { Dialog, DialogHeader } from '@astryxdesign/core/Dialog';
 import { FormLayout } from '@astryxdesign/core/FormLayout';
 import { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout';
 import { Button, HStack, NumberInput, TextInput, useUiLocale } from '@maka/ui';
-import { getProviderSettingsCopy } from '../locales/settings-provider-copy';
+import { getProviderSettingsCopy } from '../features/connection-settings';
 
 /**
  * Introduce a model by exact id, for a provider whose catalog cannot grow on
diff --git a/apps/desktop/src/renderer/settings/provider-catalog-page.tsx b/apps/desktop/src/renderer/settings/provider-catalog-page.tsx
index a76ac5d239..36406c0fd9 100644
--- a/apps/desktop/src/renderer/settings/provider-catalog-page.tsx
+++ b/apps/desktop/src/renderer/settings/provider-catalog-page.tsx
@@ -40,11 +40,11 @@ import { AddProviderForm } from './provider-add-form';
 import { ProviderLogo, providerDisplay } from './provider-display';
 import { OAuthLoginPanel, useOAuthCards, type OAuthCardId } from './provider-oauth-section';
 import {
+  getProviderSettingsCopy,
   type ApiKeyOnboardingBridge,
   type ConnectionsBridge,
   type DesktopConnectionOnboardingIdentity,
-} from './provider-panel-shared';
-import { getProviderSettingsCopy } from '../locales/settings-provider-copy';
+} from '../features/connection-settings';
 
 type CatalogCategory = ProviderCatalogGroup | 'all' | 'recommended' | 'accounts';
 
diff --git a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
index 504478186d..3c78609a31 100644
--- a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
+++ b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
@@ -52,7 +52,6 @@ import {
 import { PasswordInput } from './password-input';
 import { SettingsExpandableRow } from './settings-expandable-row';
 import { SettingsRow } from './settings-section';
-import { getProviderSettingsCopy } from '../locales/settings-provider-copy';
 import { providerDisplay } from './provider-display';
 import { AddModelDialog } from './provider-add-model-dialog';
 import { EnabledModelManager } from './provider-enabled-model-manager';
@@ -64,9 +63,10 @@ import {
 } from './runtime-host-settings-target.js';
 import { useOAuthLoginFlow } from './use-oauth-login-flow';
 import {
+  getProviderSettingsCopy,
   providerPanelActionErrorMessage,
   type CredentialPresenceStatus,
-} from './provider-panel-shared';
+} from '../features/connection-settings';
 import type { StatusSemantic } from '@maka/ui';
 import {
   useConnectionDetail,
diff --git a/apps/desktop/src/renderer/settings/provider-connection-status.ts b/apps/desktop/src/renderer/settings/provider-connection-status.ts
index 1fbb461a5c..9cb7572500 100644
--- a/apps/desktop/src/renderer/settings/provider-connection-status.ts
+++ b/apps/desktop/src/renderer/settings/provider-connection-status.ts
@@ -26,7 +26,7 @@ import type { LlmConnection } from '@maka/core/llm-connections';
 import { isRetiredProvider } from '@maka/core/provider-registry';
 
 import type { UiLocale } from '@maka/core/ui-locale';
-import { getProviderSettingsCopy } from '../locales/settings-provider-copy.js';
+import { getProviderSettingsCopy } from '../features/connection-settings';
 import type { StatusSemantic } from '@maka/ui';
 
 export interface ConnectionChipStatus {
diff --git a/apps/desktop/src/renderer/settings/provider-enabled-model-manager.tsx b/apps/desktop/src/renderer/settings/provider-enabled-model-manager.tsx
index 4ae778fd61..fa8776d9ae 100644
--- a/apps/desktop/src/renderer/settings/provider-enabled-model-manager.tsx
+++ b/apps/desktop/src/renderer/settings/provider-enabled-model-manager.tsx
@@ -20,7 +20,7 @@
 import { MultiSelector } from '@astryxdesign/core';
 import type { ModelCatalogEntry } from '@maka/core/model-catalog';
 import { useUiLocale } from '@maka/ui';
-import { getProviderSettingsCopy } from '../locales/settings-provider-copy';
+import { getProviderSettingsCopy } from '../features/connection-settings';
 
 /**
  * Adapts Maka's model catalog to Astryx's multi-select field.
diff --git a/apps/desktop/src/renderer/settings/provider-oauth-section.tsx b/apps/desktop/src/renderer/settings/provider-oauth-section.tsx
index d1eb34de33..781de7e3a3 100644
--- a/apps/desktop/src/renderer/settings/provider-oauth-section.tsx
+++ b/apps/desktop/src/renderer/settings/provider-oauth-section.tsx
@@ -27,7 +27,7 @@ import {
   useMountedRef,
   useUiLocale,
 } from '@maka/ui';
-import { getProviderSettingsCopy, type ProviderSettingsCopy } from '../locales/settings-provider-copy';
+import { getProviderSettingsCopy, type ProviderSettingsCopy } from '../features/connection-settings';
 import {
   useOAuthLoginFlow,
   subscriptionActionErrorMessage,
diff --git a/apps/desktop/src/renderer/settings/providers-panel.tsx b/apps/desktop/src/renderer/settings/providers-panel.tsx
index d13f20f053..99a23da49d 100644
--- a/apps/desktop/src/renderer/settings/providers-panel.tsx
+++ b/apps/desktop/src/renderer/settings/providers-panel.tsx
@@ -56,18 +56,18 @@ import { SettingsRouteHeader } from './settings-route-header';
 import { ProviderLogo, providerDisplay } from './provider-display';
 import { oauthPanelSubtitle } from './provider-oauth-section';
 import {
+  getProviderSettingsCopy,
   providerPanelActionErrorMessage,
   type ApiKeyOnboardingBridge,
   type ConnectionsBridge,
   type DesktopConnectionOnboardingIdentity,
-} from './provider-panel-shared';
-import { getProviderSettingsCopy } from '../locales/settings-provider-copy';
+} from '../features/connection-settings';
 import {
   RuntimeHostSettingsGenerationBoundary,
   useRuntimeHostSettingsErrorReporter,
 } from './runtime-host-settings-target.js';
 
-export type { ConnectionsBridge } from './provider-panel-shared';
+export type { ConnectionsBridge } from '../features/connection-settings';
 
 /**
  * Where the panel is. Four levels, one container, one back affordance:
diff --git a/apps/desktop/src/renderer/settings/runtime-host-settings-bridge.ts b/apps/desktop/src/renderer/settings/runtime-host-settings-bridge.ts
index 0a4aa702d7..de724af825 100644
--- a/apps/desktop/src/renderer/settings/runtime-host-settings-bridge.ts
+++ b/apps/desktop/src/renderer/settings/runtime-host-settings-bridge.ts
@@ -22,71 +22,11 @@ import type {
   DesktopRuntimeHostRef,
   MakaBridge,
 } from '../../preload/bridge-contract.js';
-import type {
-  ApiKeyOnboardingBridge,
-  ConnectionsBridge,
-} from './provider-panel-shared.js';
 import type {
   OAuthAccountFlowBridge,
   OAuthAuthorizationFlowBridge,
 } from './use-oauth-login-flow.js';
 
-export type RuntimeHostSettingsConnectionsBridge = ConnectionsBridge & {
-  setDefaultModel(input: { slug: string; model: string } | null): Promise<void>;
-};
-
-export function runtimeHostConnectionsBridge(
-  host: DesktopRuntimeHostRef,
-): RuntimeHostSettingsConnectionsBridge {
-  return {
-    getSnapshot: () => window.maka.connections.getSnapshot(undefined, host),
-    setDefault: (connection) => window.maka.connections.setDefault(connection, host),
-    setDefaultModel: (input) => window.maka.connections.setDefaultModel(input, host),
-    create: (input) => window.maka.connections.create(input, host),
-    update: (connection, patch) => window.maka.connections.update(connection, patch, host),
-    delete: (connection) => window.maka.connections.delete(connection, host),
-    test: (connection, options) => window.maka.connections.test(connection, options, host),
-    fetchModels: (connection) => window.maka.connections.fetchModels(connection, host),
-    hasSecret: (connection) => window.maka.connections.hasSecret(connection, host),
-    getRequestHeaders: (connection) => window.maka.connections.getRequestHeaders(connection, host),
-    setRequestHeaders: (connection, headers) =>
-      window.maka.connections.setRequestHeaders(connection, headers, host),
-    subscribeEvents: (handler) =>
-      window.maka.connections.subscribeEvents(handler, host),
-  };
-}
-
-export function runtimeHostApiKeyOnboardingBridge(
-  host: DesktopRuntimeHostRef,
-): ApiKeyOnboardingBridge {
-  const targetKey = `${host.profileId}\u0000${host.hostId}`;
-  return {
-    saveUncertainty: {
-      isUncertain: () => uncertainApiKeyOnboardingTargets.has(targetKey),
-      markDispatched: () => {
-        const attemptId = nextApiKeyOnboardingAttemptId++;
-        uncertainApiKeyOnboardingTargets.set(targetKey, attemptId);
-        return attemptId;
-      },
-      settle: (attemptId) => {
-        if (uncertainApiKeyOnboardingTargets.get(targetKey) === attemptId) {
-          uncertainApiKeyOnboardingTargets.delete(targetKey);
-        }
-      },
-      restart: () => uncertainApiKeyOnboardingTargets.delete(targetKey),
-    },
-    verify: (input) => window.maka.connections.verifyOnboarding(input, host),
-    save: (input) => window.maka.connections.saveOnboarding(input, host),
-  };
-}
-
-// A Host replacement remounts the settings projection while a dispatched save
-// may still settle. Keep the safety gate target-scoped instead of form-scoped;
-// durable reconciliation across a Desktop process restart belongs to the
-// follow-up Host journal protocol.
-const uncertainApiKeyOnboardingTargets = new Map<string, number>();
-let nextApiKeyOnboardingAttemptId = 1;
-
 type RuntimeHostOAuthBridge = MakaBridge['openAiCodex'] | MakaBridge['xaiOAuth'];
 
 export function runtimeHostOAuthAuthorizationBridge(
diff --git a/apps/desktop/src/renderer/settings/settings-surface.tsx b/apps/desktop/src/renderer/settings/settings-surface.tsx
index 043e472409..73517879bf 100644
--- a/apps/desktop/src/renderer/settings/settings-surface.tsx
+++ b/apps/desktop/src/renderer/settings/settings-surface.tsx
@@ -95,10 +95,10 @@ import { WebSearchSettingsPage } from './web-search-settings-page';
 import type { UiLocaleUpdateGate } from './ui-locale-update-gate';
 import { getSettingsSharedCopy } from '../locales/settings-shared-copy.js';
 import {
-  runtimeHostApiKeyOnboardingBridge,
-  runtimeHostConnectionsBridge,
+  ConnectionSettingsServicesConsumer,
+  type ConnectionSettingsServices,
   type RuntimeHostSettingsConnectionsBridge,
-} from './runtime-host-settings-bridge.js';
+} from '../features/connection-settings';
 import {
   hasRuntimeHostSettingsPatch,
   projectClientOwnedSettings,
@@ -149,7 +149,7 @@ function readyRuntimeHost(
   return { profileId: entry.profile.id, hostId: entry.hostId };
 }
 
-export function SettingsSurface(props: {
+type SettingsSurfaceProps = {
   onClose(): void;
   themePref: ThemePreference;
   onThemeChange(pref: ThemePreference): void;
@@ -172,7 +172,26 @@ export function SettingsSurface(props: {
   onRemoteHostAdded(profileId: string): void;
   onSelectedRuntimeHostProfileIdChange(profileId: string | undefined): void;
   snapshotCache?: SettingsSnapshotCache;
-}) {
+};
+
+export function SettingsSurface(props: SettingsSurfaceProps) {
+  return (
+    <ConnectionSettingsServicesConsumer>
+      {(connectionSettingsServices) => (
+        <SettingsSurfaceContent
+          {...props}
+          connectionSettingsServices={connectionSettingsServices}
+        />
+      )}
+    </ConnectionSettingsServicesConsumer>
+  );
+}
+
+function SettingsSurfaceContent(
+  props: SettingsSurfaceProps & {
+    readonly connectionSettingsServices: ConnectionSettingsServices;
+  },
+) {
   const locale = useUiLocale();
   const copy = getSettingsSharedCopy(locale);
   const localizedNav = groupedNav(locale);
@@ -349,18 +368,14 @@ export function SettingsSurface(props: {
     ),
     [runtimeHostLifecycleByProfile, runtimeHosts, selectedProfileId],
   );
-  const connectionsBridge = useMemo(
-    () => selectedRuntimeHost
-      ? runtimeHostConnectionsBridge(selectedRuntimeHost)
-      : undefined,
-    [selectedRuntimeHost],
-  );
-  const apiKeyOnboardingBridge = useMemo(
+  const connectionBridges = useMemo(
     () => selectedRuntimeHost
-      ? runtimeHostApiKeyOnboardingBridge(selectedRuntimeHost)
+      ? props.connectionSettingsServices.forHost(selectedRuntimeHost)
       : undefined,
-    [selectedRuntimeHost],
+    [props.connectionSettingsServices, selectedRuntimeHost],
   );
+  const connectionsBridge = connectionBridges?.connections;
+  const apiKeyOnboardingBridge = connectionBridges?.apiKeyOnboarding;
   const selectedRuntimeHostKey = selectedRuntimeHost
     ? runtimeHostSettingsKey(selectedRuntimeHost)
     : undefined;
@@ -1078,7 +1093,9 @@ function SettingsPageBody(props: {
   usageStats: UsageStats | null;
   connections: ProjectedLlmConnection[];
   connectionsBridge: RuntimeHostSettingsConnectionsBridge | undefined;
-  apiKeyOnboardingBridge: ReturnType<typeof runtimeHostApiKeyOnboardingBridge> | undefined;
+  apiKeyOnboardingBridge:
+    | ReturnType<ConnectionSettingsServices['forHost']>['apiKeyOnboarding']
+    | undefined;
   defaultSlug: string | null;
   runtimeHost: DesktopRuntimeHostRef | undefined;
   runtimeHostAvailabilityStatus: RuntimeHostAvailabilityStatus;
diff --git a/apps/desktop/src/renderer/settings/use-connection-detail.ts b/apps/desktop/src/renderer/settings/use-connection-detail.ts
index dcbbfb709b..75ac12bf57 100644
--- a/apps/desktop/src/renderer/settings/use-connection-detail.ts
+++ b/apps/desktop/src/renderer/settings/use-connection-detail.ts
@@ -46,7 +46,6 @@ import {
   providerSupportsModelDiscovery,
 } from '@maka/core/llm-connections';
 import { useMountedRef, useToast, useUiLocale } from '@maka/ui';
-import { getProviderSettingsCopy } from '../locales/settings-provider-copy';
 import { connectionChipStatus } from './provider-connection-status';
 import { relayProfileDraftReseedPlan, relayProfileDraftSeed } from './relay-profile-draft';
 import { applyBulkThinkingLevel, relayProfileWithThinkingLevels } from './relay-thinking-bulk';
@@ -58,10 +57,11 @@ import type {
 import {
   connectionLastTestMessageDisplay,
   connectionTestFailureMessage,
+  getProviderSettingsCopy,
   providerPanelActionErrorMessage,
   type ConnectionsBridge,
   type CredentialPresenceStatus,
-} from './provider-panel-shared';
+} from '../features/connection-settings';
 import {
   useRuntimeHostSettingsErrorReporter,
   useRuntimeHostSettingsTarget,
diff --git a/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts b/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts
index 42ab18ee47..034b16d365 100644
--- a/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts
+++ b/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts
@@ -26,7 +26,7 @@ import {
   useUiLocale,
 } from '@maka/ui';
 import { createOneShotActionGuard, teardownPendingAuthorization } from './oauth-login-flow-guard';
-import { getProviderSettingsCopy } from '../locales/settings-provider-copy';
+import { getProviderSettingsCopy } from '../features/connection-settings';
 import { useRuntimeHostSettingsErrorReporter } from './runtime-host-settings-target.js';
 
 // Shared browser-assisted OAuth login-flow controller (device-code polling).
diff --git a/apps/desktop/stories/settings/provider-settings.stories.tsx b/apps/desktop/stories/settings/provider-settings.stories.tsx
index 444481fab1..059a388cc7 100644
--- a/apps/desktop/stories/settings/provider-settings.stories.tsx
+++ b/apps/desktop/stories/settings/provider-settings.stories.tsx
@@ -35,7 +35,7 @@ import { buildChatModelChoices } from '@maka/core/chat-model-choice';
 import { ProvidersPanel, type ConnectionsBridge } from '../../src/renderer/settings/providers-panel';
 import { RuntimeHostSettingsTarget } from '../../src/renderer/settings/runtime-host-settings-target';
 import { SettingsPage } from '../../src/renderer/settings/settings-section';
-import type { ApiKeyOnboardingBridge } from '../../src/renderer/settings/provider-panel-shared';
+import type { ApiKeyOnboardingBridge } from '../../src/renderer/features/connection-settings';
 
 const NOW = Date.parse('2026-07-01T08:00:00Z');
 
diff --git a/apps/desktop/stories/settings/settings-pages.stories.tsx b/apps/desktop/stories/settings/settings-pages.stories.tsx
index 8fcc2fa5a3..5195fe534f 100644
--- a/apps/desktop/stories/settings/settings-pages.stories.tsx
+++ b/apps/desktop/stories/settings/settings-pages.stories.tsx
@@ -55,6 +55,8 @@ import { buildHealthSnapshot } from '@maka/core/health';
 import { createDefaultSettings, mergeSettings } from '@maka/core/settings';
 import { DEFAULT_DAILY_REVIEW_CONFIG } from '@maka/core/daily-review';
 import { SettingsSurface } from '../../src/renderer/settings/settings-surface';
+import { ConnectionSettingsServicesProvider } from '../../src/renderer/features/connection-settings';
+import { createDesktopConnectionSettingsServices } from '../../src/renderer/platform/desktop/create-connection-settings-services';
 import { createUiLocaleUpdateGate } from '../../src/renderer/settings/ui-locale-update-gate';
 import {
   createSettingsSnapshotCache,
@@ -1599,6 +1601,9 @@ function SettingsStoryFrame(props: SettingsStoryProps) {
   // holds both in AppShell state and applies them optimistically on click.
   const [themePref, setThemePref] = useState<ThemePreference>('auto');
   const [themePalette, setThemePalette] = useState<ThemePalette>('default');
+  const [connectionSettingsServices] = useState(
+    createDesktopConnectionSettingsServices,
+  );
 
   return (
     <>
@@ -1616,27 +1621,29 @@ function SettingsStoryFrame(props: SettingsStoryProps) {
           minHeight: 640,
         }}
       >
-        <SettingsSurface
-          onClose={noop}
-          themePref={themePref}
-          onThemeChange={setThemePref}
-          themePalette={themePalette}
-          onThemePaletteChange={setThemePalette}
-          onUiLocalePreferenceChange={noop}
-          uiLocaleUpdateGate={uiLocaleUpdateGate}
-          onDefaultPermissionModeChange={noop}
-          request={{ section: props.section }}
-          openProviderCatalog={props.openProviderCatalog}
-          initialConnectionSlug={props.initialConnectionSlug}
-          initialFocusRef={initialFocusRef}
-          onOpenDailyReview={noop}
-          onOpenSession={noop}
-          archivedTasks={archivedTasks}
-          onTaskImported={noop}
-          onRemoteHostAdded={noop}
-          onSelectedRuntimeHostProfileIdChange={noop}
-          snapshotCache={snapshotCache}
-        />
+        <ConnectionSettingsServicesProvider services={connectionSettingsServices}>
+          <SettingsSurface
+            onClose={noop}
+            themePref={themePref}
+            onThemeChange={setThemePref}
+            themePalette={themePalette}
+            onThemePaletteChange={setThemePalette}
+            onUiLocalePreferenceChange={noop}
+            uiLocaleUpdateGate={uiLocaleUpdateGate}
+            onDefaultPermissionModeChange={noop}
+            request={{ section: props.section }}
+            openProviderCatalog={props.openProviderCatalog}
+            initialConnectionSlug={props.initialConnectionSlug}
+            initialFocusRef={initialFocusRef}
+            onOpenDailyReview={noop}
+            onOpenSession={noop}
+            archivedTasks={archivedTasks}
+            onTaskImported={noop}
+            onRemoteHostAdded={noop}
+            onSelectedRuntimeHostProfileIdChange={noop}
+            snapshotCache={snapshotCache}
+          />
+        </ConnectionSettingsServicesProvider>
       </div>
     </>
   );


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org