From 20844b9fa382acefcb63ce3e059549d954a33724 Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Thu, 21 May 2026 22:20:05 +0530 Subject: [PATCH 1/5] fix(config): log RPC URL and core mode as strings, not object wrappers storeRpcUrl logged { url: '...' } and storeCoreMode logged { mode } causing "[object Object]" in diagnostic output and hiding the actual value in user-reported logs. Log the string values directly. Adds a regression test asserting no object argument appears in the debug log call for storeRpcUrl. Refs #2437 (sub-issue B) --- app/src/utils/__tests__/configPersistence.test.ts | 12 ++++++++++++ app/src/utils/configPersistence.ts | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/src/utils/__tests__/configPersistence.test.ts b/app/src/utils/__tests__/configPersistence.test.ts index b1b9b537de..64ed2ceb04 100644 --- a/app/src/utils/__tests__/configPersistence.test.ts +++ b/app/src/utils/__tests__/configPersistence.test.ts @@ -88,6 +88,18 @@ describe('configPersistence', () => { storeRpcUrl(' '); expect(localStorage.getItem(STORAGE_KEY)).toBeNull(); }); + + it('logs the URL string directly, not an object wrapper', () => { + const spy = vi.spyOn(console, 'debug').mockImplementation(() => {}); + storeRpcUrl('http://localhost:9000/rpc'); + const calls = spy.mock.calls.flat(); + // Must NOT log an object like { url: '...' } — that renders as [object Object] + const hasObjectArg = calls.some((arg) => typeof arg === 'object' && arg !== null); + expect(hasObjectArg).toBe(false); + const urlArg = calls.find((arg) => typeof arg === 'string' && arg.includes('localhost')); + expect(urlArg).toBe('http://localhost:9000/rpc'); + spy.mockRestore(); + }); }); describe('clearStoredRpcUrl', () => { diff --git a/app/src/utils/configPersistence.ts b/app/src/utils/configPersistence.ts index 1e2e62b1cb..0505068ecd 100644 --- a/app/src/utils/configPersistence.ts +++ b/app/src/utils/configPersistence.ts @@ -85,7 +85,7 @@ export function storeRpcUrl(url: string): void { try { if (url && url.trim().length > 0) { localStorage.setItem(RPC_URL_STORAGE_KEY, url.trim()); - console.debug('[configPersistence] Stored RPC URL:', { url: url.trim() }); + console.debug('[configPersistence] Stored RPC URL:', url.trim()); } else { // Allow clearing the stored URL to reset to default localStorage.removeItem(RPC_URL_STORAGE_KEY); @@ -255,7 +255,7 @@ export function getStoredCoreMode(): 'local' | 'cloud' | null { export function storeCoreMode(mode: 'local' | 'cloud'): void { try { localStorage.setItem(CORE_MODE_STORAGE_KEY, mode); - console.debug('[configPersistence] Stored core mode:', { mode }); + console.debug('[configPersistence] Stored core mode:', mode); } catch { console.warn('[configPersistence] Unable to store core mode in localStorage'); } From 271d476435fe34a71005b75acfab8f73161111bf Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Thu, 21 May 2026 22:21:17 +0530 Subject: [PATCH 2/5] chore: apply prettier auto-fixes to configPersistence test --- app/src/utils/__tests__/configPersistence.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/utils/__tests__/configPersistence.test.ts b/app/src/utils/__tests__/configPersistence.test.ts index 64ed2ceb04..d197351a4c 100644 --- a/app/src/utils/__tests__/configPersistence.test.ts +++ b/app/src/utils/__tests__/configPersistence.test.ts @@ -94,9 +94,9 @@ describe('configPersistence', () => { storeRpcUrl('http://localhost:9000/rpc'); const calls = spy.mock.calls.flat(); // Must NOT log an object like { url: '...' } — that renders as [object Object] - const hasObjectArg = calls.some((arg) => typeof arg === 'object' && arg !== null); + const hasObjectArg = calls.some(arg => typeof arg === 'object' && arg !== null); expect(hasObjectArg).toBe(false); - const urlArg = calls.find((arg) => typeof arg === 'string' && arg.includes('localhost')); + const urlArg = calls.find(arg => typeof arg === 'string' && arg.includes('localhost')); expect(urlArg).toBe('http://localhost:9000/rpc'); spy.mockRestore(); }); From d0dce05282739793206544ecb764e7285dccb614 Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Thu, 21 May 2026 23:30:46 +0530 Subject: [PATCH 3/5] test(config-persistence): cover storeCoreMode log format (address graycyrus review) Add regression test verifying storeCoreMode logs the mode string directly rather than an object wrapper, mirroring the existing storeRpcUrl test. Requested in reviewer comment: "not blocking since it's a trivial one-line change and the pattern is proven by the existing test, but worth adding." --- app/src/utils/__tests__/configPersistence.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/utils/__tests__/configPersistence.test.ts b/app/src/utils/__tests__/configPersistence.test.ts index d197351a4c..8d3fa5b20e 100644 --- a/app/src/utils/__tests__/configPersistence.test.ts +++ b/app/src/utils/__tests__/configPersistence.test.ts @@ -430,6 +430,18 @@ describe('configPersistence', () => { expect(getStoredCoreMode()).toBeNull(); }); + it('logs the mode string directly, not an object wrapper', () => { + const spy = vi.spyOn(console, 'debug').mockImplementation(() => {}); + storeCoreMode('cloud'); + const calls = spy.mock.calls.flat(); + // Must NOT log an object like { mode } — that renders as [object Object] + const hasObjectArg = calls.some(arg => typeof arg === 'object' && arg !== null); + expect(hasObjectArg).toBe(false); + const modeArg = calls.find(arg => typeof arg === 'string' && arg === 'cloud'); + expect(modeArg).toBe('cloud'); + spy.mockRestore(); + }); + it('falls back to the E2E default local mode when no marker has been written', async () => { vi.resetModules(); vi.doMock('../config', () => ({ From 1e6f1239845aac7a806b798859b08b30655cffb3 Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Thu, 21 May 2026 23:53:25 +0530 Subject: [PATCH 4/5] test(config-persistence): wrap spy assertions in try/finally (CodeRabbit) Ensure mockRestore() always runs even when an assertion throws, preventing console.debug spy from leaking into subsequent tests. Applied to both the storeRpcUrl and storeCoreMode log-format tests. --- .../utils/__tests__/configPersistence.test.ts | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/app/src/utils/__tests__/configPersistence.test.ts b/app/src/utils/__tests__/configPersistence.test.ts index 8d3fa5b20e..eb1f80972d 100644 --- a/app/src/utils/__tests__/configPersistence.test.ts +++ b/app/src/utils/__tests__/configPersistence.test.ts @@ -91,14 +91,17 @@ describe('configPersistence', () => { it('logs the URL string directly, not an object wrapper', () => { const spy = vi.spyOn(console, 'debug').mockImplementation(() => {}); - storeRpcUrl('http://localhost:9000/rpc'); - const calls = spy.mock.calls.flat(); - // Must NOT log an object like { url: '...' } — that renders as [object Object] - const hasObjectArg = calls.some(arg => typeof arg === 'object' && arg !== null); - expect(hasObjectArg).toBe(false); - const urlArg = calls.find(arg => typeof arg === 'string' && arg.includes('localhost')); - expect(urlArg).toBe('http://localhost:9000/rpc'); - spy.mockRestore(); + try { + storeRpcUrl('http://localhost:9000/rpc'); + const calls = spy.mock.calls.flat(); + // Must NOT log an object like { url: '...' } — that renders as [object Object] + const hasObjectArg = calls.some(arg => typeof arg === 'object' && arg !== null); + expect(hasObjectArg).toBe(false); + const urlArg = calls.find(arg => typeof arg === 'string' && arg.includes('localhost')); + expect(urlArg).toBe('http://localhost:9000/rpc'); + } finally { + spy.mockRestore(); + } }); }); @@ -432,14 +435,17 @@ describe('configPersistence', () => { it('logs the mode string directly, not an object wrapper', () => { const spy = vi.spyOn(console, 'debug').mockImplementation(() => {}); - storeCoreMode('cloud'); - const calls = spy.mock.calls.flat(); - // Must NOT log an object like { mode } — that renders as [object Object] - const hasObjectArg = calls.some(arg => typeof arg === 'object' && arg !== null); - expect(hasObjectArg).toBe(false); - const modeArg = calls.find(arg => typeof arg === 'string' && arg === 'cloud'); - expect(modeArg).toBe('cloud'); - spy.mockRestore(); + try { + storeCoreMode('cloud'); + const calls = spy.mock.calls.flat(); + // Must NOT log an object like { mode } — that renders as [object Object] + const hasObjectArg = calls.some(arg => typeof arg === 'object' && arg !== null); + expect(hasObjectArg).toBe(false); + const modeArg = calls.find(arg => typeof arg === 'string' && arg === 'cloud'); + expect(modeArg).toBe('cloud'); + } finally { + spy.mockRestore(); + } }); it('falls back to the E2E default local mode when no marker has been written', async () => { From f52607d155b64fa8abb9ec463e7adcf3e3b6acdb Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 22 May 2026 17:56:47 -0700 Subject: [PATCH 5/5] fix(i18n): remove duplicate German MCP server keys re-introduced by merge The merge with main re-introduced the duplicate keys that #2495 had already removed, because the PR branch and main both held copies in different chunk positions. Drop the second occurrence to restore Type Check green. --- app/src/lib/i18n/chunks/de-5.ts | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/app/src/lib/i18n/chunks/de-5.ts b/app/src/lib/i18n/chunks/de-5.ts index 79f041cc19..c8a26af5f3 100644 --- a/app/src/lib/i18n/chunks/de-5.ts +++ b/app/src/lib/i18n/chunks/de-5.ts @@ -523,28 +523,6 @@ const de5: TranslationMap = { 'settings.mascot.colorYellow': 'Gelb', 'settings.mascot.libraryUnavailable': 'OpenHuman Bibliothek nicht verfügbar', 'settings.mascot.title': 'OpenHuman', - 'settings.developerMenu.mcpServer.title': 'MCP-Server', - 'settings.developerMenu.mcpServer.desc': - 'Externe MCP-Clients zur Verbindung mit OpenHuman konfigurieren', - 'settings.mcpServer.title': 'MCP-Server', - 'settings.mcpServer.toolsSectionTitle': 'Verfügbare Tools', - 'settings.mcpServer.toolsSectionDesc': - 'Tools, die über den MCP-Stdio-Server bereitgestellt werden, wenn openhuman-core mcp ausgeführt wird', - 'settings.mcpServer.configSectionTitle': 'Client-Konfiguration', - 'settings.mcpServer.configSectionDesc': - 'Wählen Sie Ihren MCP-Client aus, um den passenden Konfigurations-Schnipsel zu erzeugen', - 'settings.mcpServer.copySnippet': 'In Zwischenablage kopieren', - 'settings.mcpServer.copied': 'Kopiert!', - 'settings.mcpServer.openConfigFile': 'Konfigurationsdatei öffnen', - 'settings.mcpServer.binaryPathNotFound': - 'OpenHuman-Binary nicht gefunden. Wenn Sie aus dem Quellcode arbeiten, bauen Sie mit: cargo build --bin openhuman-core', - 'settings.mcpServer.openConfigError': 'Konfigurationsdatei konnte nicht geöffnet werden', - 'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop', - 'settings.mcpServer.clientCursor': 'Cursor', - 'settings.mcpServer.clientCodex': 'Codex', - 'settings.mcpServer.clientZed': 'Zed', - 'settings.mcpServer.configFilePath': 'Konfigurationsdatei', - 'settings.mcpServer.clientSelectorAriaLabel': 'MCP-Client-Auswahl', }; export default de5;