@@ -92,14 +118,25 @@ export function NativeShellConnectionManager({ open, shellApi, shellState, onClo
+ {isDesktopShell && (
+
+ )}
+
{shellState.profiles.length === 0 ? (
{t("shell.noServersSaved", "No remote servers saved yet.")}
@@ -128,7 +165,7 @@ export function NativeShellConnectionManager({ open, shellApi, shellState, onClo
{profile.name}
{profile.serverUrl}
- {profile.id === shellState.activeProfileId &&
{t("shell.activePill", "Active")}}
+ {isProfileActive(profile) &&
{t("shell.activePill", "Active")}}
-
+
diff --git a/packages/dashboard/app/components/__tests__/NativeShellConnectionManager.test.tsx b/packages/dashboard/app/components/__tests__/NativeShellConnectionManager.test.tsx
index 6f1f4b0bfe..f95d0b7056 100644
--- a/packages/dashboard/app/components/__tests__/NativeShellConnectionManager.test.tsx
+++ b/packages/dashboard/app/components/__tests__/NativeShellConnectionManager.test.tsx
@@ -22,20 +22,81 @@ function createShellApi() {
};
}
+const remoteProfile = {
+ id: "remote-1",
+ name: "Remote",
+ serverUrl: "https://fusion.example.com",
+ authToken: "token-value",
+ createdAt: "",
+ updatedAt: "",
+};
+
describe("NativeShellConnectionManager", () => {
- it("switches desktop mode", async () => {
+ it("shows a desktop Local Server destination and switches back from an active remote profile", async () => {
const shellApi = createShellApi();
render(
,
);
- fireEvent.click(screen.getByText("Local"));
+ expect(screen.getByRole("button", { name: /Local Server/i })).toBeInTheDocument();
+ expect(screen.getByText("Remote")).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole("button", { name: /Local Server/i }));
+
await waitFor(() => expect(shellApi.setDesktopMode).toHaveBeenCalledWith("local"));
+ expect(shellApi.setActiveProfile).not.toHaveBeenCalled();
+ });
+
+ it("marks Local Server active in desktop local mode and switches remote profile use into remote mode", async () => {
+ const shellApi = createShellApi();
+ render(
+
,
+ );
+
+ expect(screen.getByRole("button", { name: /Local Server/i })).toHaveAttribute("aria-pressed", "true");
+ expect(screen.getByText("Active")).toBeInTheDocument();
+
+ fireEvent.click(screen.getAllByLabelText("Use Remote")[1]!);
+
+ await waitFor(() => {
+ expect(shellApi.setDesktopMode).toHaveBeenCalledWith("remote");
+ expect(shellApi.setActiveProfile).toHaveBeenCalledWith("remote-2");
+ });
+ expect(shellApi.setDesktopMode.mock.invocationCallOrder[0]).toBeLessThan(shellApi.setActiveProfile.mock.invocationCallOrder[0]);
+ });
+
+ it("renders Local Server deterministically with no remote profiles and an unset desktop mode", () => {
+ const shellApi = createShellApi();
+ render(
+
,
+ );
+
+ expect(screen.getByRole("button", { name: /Local Server/i })).toBeInTheDocument();
+ expect(screen.getByText("No remote servers saved yet.")).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: "Add server" })).toBeInTheDocument();
});
it("shows active profile indicator and requires delete confirmation", async () => {
@@ -49,6 +110,7 @@ describe("NativeShellConnectionManager", () => {
/>,
);
+ expect(screen.queryByRole("button", { name: /Local Server/i })).toBeNull();
expect(screen.getByText("Active")).toBeInTheDocument();
fireEvent.click(screen.getByLabelText("Delete Prod"));
expect(screen.getByRole("alertdialog", { name: "Delete server confirmation" })).toBeInTheDocument();
@@ -79,7 +141,7 @@ describe("NativeShellConnectionManager", () => {
});
});
- it("supports empty-state recovery and QR import", async () => {
+ it("supports empty-state recovery and QR import without a mobile Local Server option", async () => {
const shellApi = createShellApi();
render(
{
/>,
);
+ expect(screen.queryByRole("button", { name: /Local Server/i })).toBeNull();
expect(screen.getByText("No remote servers saved yet.")).toBeInTheDocument();
fireEvent.click(screen.getAllByText("Scan QR")[0]!);
diff --git a/packages/i18n/locales/en/app.json b/packages/i18n/locales/en/app.json
index b810bd31a7..ac071cb0dd 100644
--- a/packages/i18n/locales/en/app.json
+++ b/packages/i18n/locales/en/app.json
@@ -6947,7 +6947,10 @@
"serverUrlLabel": "Server URL",
"serverUrlProtocolError": "Server URL must use http or https",
"use": "Use",
- "useProfile": "Use {{name}}"
+ "useProfile": "Use {{name}}",
+ "localServerTitle": "Local Server",
+ "localServerDescription": "Use the embedded Fusion server on this device.",
+ "localServerActive": "Current"
},
"skills": {
"addSkill": "Add a skill…",
diff --git a/packages/i18n/locales/es/app.json b/packages/i18n/locales/es/app.json
index 48b29297bf..030ddad2a4 100644
--- a/packages/i18n/locales/es/app.json
+++ b/packages/i18n/locales/es/app.json
@@ -6937,7 +6937,10 @@
"serverUrlLabel": "URL del servidor",
"serverUrlProtocolError": "La URL del servidor debe usar http o https",
"use": "Usar",
- "useProfile": "Usar {{name}}"
+ "useProfile": "Usar {{name}}",
+ "localServerTitle": "Servidor local",
+ "localServerDescription": "Usa el servidor Fusion integrado en este dispositivo.",
+ "localServerActive": "Actual"
},
"skills": {
"addSkill": "Añadir una habilidad…",
diff --git a/packages/i18n/locales/fr/app.json b/packages/i18n/locales/fr/app.json
index 0a1410ffc9..6f35abc2fc 100644
--- a/packages/i18n/locales/fr/app.json
+++ b/packages/i18n/locales/fr/app.json
@@ -6937,7 +6937,10 @@
"serverUrlLabel": "URL du serveur",
"serverUrlProtocolError": "L'URL du serveur doit utiliser http ou https",
"use": "Utiliser",
- "useProfile": "Utiliser {{name}}"
+ "useProfile": "Utiliser {{name}}",
+ "localServerTitle": "Serveur local",
+ "localServerDescription": "Utiliser le serveur Fusion intégré sur cet appareil.",
+ "localServerActive": "Actuel"
},
"skills": {
"addSkill": "Ajouter une compétence…",
diff --git a/packages/i18n/locales/ko/app.json b/packages/i18n/locales/ko/app.json
index 2e07f1e3df..9f0415402d 100644
--- a/packages/i18n/locales/ko/app.json
+++ b/packages/i18n/locales/ko/app.json
@@ -6937,7 +6937,10 @@
"serverUrlLabel": "서버 URL",
"serverUrlProtocolError": "서버 URL은 http 또는 https를 사용해야 합니다",
"use": "사용",
- "useProfile": "{{name}} 사용"
+ "useProfile": "{{name}} 사용",
+ "localServerTitle": "로컬 서버",
+ "localServerDescription": "이 기기의 내장 Fusion 서버를 사용합니다.",
+ "localServerActive": "현재"
},
"skills": {
"addSkill": "스킬 추가…",
diff --git a/packages/i18n/locales/zh-CN/app.json b/packages/i18n/locales/zh-CN/app.json
index d3fd442234..b7bd443c3c 100644
--- a/packages/i18n/locales/zh-CN/app.json
+++ b/packages/i18n/locales/zh-CN/app.json
@@ -6937,7 +6937,10 @@
"serverUrlLabel": "服务器 URL",
"serverUrlProtocolError": "服务器 URL 必须使用 http 或 https",
"use": "使用",
- "useProfile": "使用 {{name}}"
+ "useProfile": "使用 {{name}}",
+ "localServerTitle": "本地服务器",
+ "localServerDescription": "使用此设备上的内置 Fusion 服务器。",
+ "localServerActive": "当前"
},
"skills": {
"addSkill": "添加技能…",
diff --git a/packages/i18n/locales/zh-TW/app.json b/packages/i18n/locales/zh-TW/app.json
index b2052bbcbd..619e755202 100644
--- a/packages/i18n/locales/zh-TW/app.json
+++ b/packages/i18n/locales/zh-TW/app.json
@@ -6937,7 +6937,10 @@
"serverUrlLabel": "伺服器 URL",
"serverUrlProtocolError": "伺服器 URL 必須使用 http 或 https",
"use": "使用",
- "useProfile": "使用 {{name}}"
+ "useProfile": "使用 {{name}}",
+ "localServerTitle": "本機伺服器",
+ "localServerDescription": "使用此裝置上的內建 Fusion 伺服器。",
+ "localServerActive": "目前"
},
"skills": {
"addSkill": "新增技能…",
diff --git a/packages/i18n/src/resources.d.ts b/packages/i18n/src/resources.d.ts
index 289bf4b173..e5284a1fd6 100644
--- a/packages/i18n/src/resources.d.ts
+++ b/packages/i18n/src/resources.d.ts
@@ -6901,6 +6901,9 @@ export default interface Resources {
"deleteConfirmMessage": "Delete {{name}}? This removes the saved profile.",
"deleteProfile": "Delete {{name}}",
"editProfile": "Edit {{name}}",
+ "localServerActive": "Current",
+ "localServerDescription": "Use the embedded Fusion server on this device.",
+ "localServerTitle": "Local Server",
"modeLocal": "Local",
"modeRemote": "Remote",
"nameLabel": "Name",