Skip to content

feat(settings): first-class remote-core connection setting + live status (GH-4396)#4406

Open
M3gA-Mind wants to merge 2 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/GH-4396-remote-core-settings
Open

feat(settings): first-class remote-core connection setting + live status (GH-4396)#4406
M3gA-Mind wants to merge 2 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/GH-4396-remote-core-settings

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a first-class Settings → Core connection panel that promotes the pre-existing "cloud" core mode (a persisted remote-core RPC URL + bearer token) out of the pre-router BootCheckGate picker and into the running app.
  • Adds a live connect/failure status indicator (the core ask): a status dot + text driven by a real core.ping against the currently-active core, with a Recheck button.
  • Adds a "use remote core" toggle plus URL/token fields with a Test connection button, and a Save & restart action that re-enters the existing boot flow.
  • Reuses existing plumbing (coreModeSlice, configPersistence, testCoreRpcConnection) — no second remote-core mechanism introduced.
  • 12 new settings.core.* i18n keys translated across all 13 locales.

Problem

Pointing the desktop client at a remote openhuman-core was only reachable two ways: the pre-router BootCheckGate picker (first launch / "Switch mode"), or shell-level env vars. Normal Windows launch paths (Start Menu shortcut, openhuman://auth/... deep link) cannot carry those env vars, so the env-var attach path silently fell back to spawning the in-process core, and there was no persistent in-app affordance or visible status for a remote core once past the boot gate. See #4396 (split from #2437-A).

Scope was agreed as promote the existing cloud mode + surface status, not a greenfield build.

Solution

  • CoreConnectionPanel.tsx (new): live status indicator (connected remote/local, token-rejected, unreachable, checking) + Recheck; a SettingsSwitch remote toggle; URL + token fields reusing the BootCheckGate cloud-picker validation and testCoreRpcConnection; and Save & restart which persists via storeRpcUrl/storeCoreToken/storeCoreMode, dispatches setCoreMode, clears the RPC caches, and calls restartApp() so the normal BootCheckGate flow re-runs. Save is gated on a dirty check.
  • Registry + routing: new core entry + <Route path="core">.
  • i18n: settings.core.* keys added to en.ts and all 13 locales with real translations (parity gate green).

Design decisions / tradeoffs:

  • Boot-gate hard-fail/fallback semantics unchanged — the panel only surfaces connection state and re-enters the existing flow on save.
  • Env-var attach path (OPENHUMAN_CORE_REUSE_EXISTING) left as documented dev-only and intentionally not surfaced in the UI.
  • Token stays on the existing localStorage path the cloud picker already uses; an in-code comment references security audit U3 as the known OS-keychain migration follow-up (not blocked here).
  • Deep-link attach?url=&token= import is out of scope (deferred; needs security review — phishing/pivot vector).
  • Env-var naming discrepancy: the issue references OPENHUMAN_CORE_URL, which does not exist in the codebase. The real vars are OPENHUMAN_CORE_RPC_URL (set by the core to advertise its own bound port — internal discovery, not a point-at-remote input) and the build-time VITE_OPENHUMAN_CORE_RPC_URL. The actual user-facing remote-core URL is the persisted cloud-mode URL this panel edits.
  • IA note: there is no "Advanced" settings group in the current registry, so the panel was placed in the General group directly above About (which already shows core mode read-only). Easy to relocate to a dedicated Advanced group if preferred.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — CoreConnectionPanel.test.tsx: status rendering per mode, reveal toggle, unreachable failure path, and the persist/dispatch/restart save flow.
  • Diff coverage ≥ 80% — Vitest added; measured 131/145 = 90.3% line coverage on CoreConnectionPanel.tsx locally (11 tests). CI diff-cover confirms the merged gate on changed lines.
  • Coverage matrix updated — N/A: no matrix feature row maps to this settings panel (UI surface over existing cloud-mode behaviour).
  • All affected feature IDs listed under ## RelatedN/A: none.
  • No new external network dependencies introduced — no new deps; reuses testCoreRpcConnection (mock-friendly).
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: additive Settings panel, no release-cut surface change.
  • Linked issue closed via Closes #NNN — see ## Related.

Impact

  • Desktop/web: additive Settings panel; no change to existing local/cloud runtime behaviour or the boot gate. Web builds (cloud-only) also get the in-app surface.
  • Security: no new secret exposure beyond the existing cloud-mode localStorage token (audit U3, noted in-code as follow-up); public-HTTP warning reused from the boot picker.
  • Migration/compat: none — reuses existing coreMode slice + configPersistence keys.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/GH-4396-remote-core-settings
  • Commit SHA: c3fa32c

Validation Run

  • pnpm --filter openhuman-app format:check — passes
  • pnpm typecheck — 0 errors in changed files (one pre-existing unrelated error: rehype-highlight missing in AgentMessageBubble.tsx, untouched here)
  • Focused tests: CoreConnectionPanel.test.tsx — 11/11 pass; pnpm --filter openhuman-app lint 0 errors; pnpm i18n:check 0 missing/0 extra across 13 locales
  • Rust fmt/check (if changed): N/A — no Rust changed
  • Tauri fmt/check (if changed): N/A — no Tauri/Rust changed

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: remote-core URL/token become editable from Settings with a live status indicator; switching mode there persists + restarts into the existing boot flow.
  • User-visible effect: new Settings → Core connection panel; no change for users who stay on local mode.

Parity Contract

  • Legacy behavior preserved: BootCheckGate picker, coreMode slice, configPersistence keys, and boot-gate fallback semantics are unchanged.
  • Guard/fallback/dispatch parity checks: save path mirrors the picker's persist + setCoreMode dispatch + cache-clear sequence; env-var attach path untouched.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this
  • Resolution (closed/superseded/updated): N/A

…tus (tinyhumansaiGH-4396)

Promote the pre-existing cloud core mode (persisted remote-core RPC URL +
bearer token, previously reachable only from the pre-router BootCheckGate
picker) into a first-class Settings > Core connection panel, and add a live
connect/failure status indicator.

- New CoreConnectionPanel: live status dot + Recheck, a 'use remote core'
  toggle, persisted URL + token fields with Test connection, and a
  Save & restart action that re-enters the existing BootCheckGate flow.
- Reuses existing plumbing (coreModeSlice, configPersistence,
  testCoreRpcConnection); does not introduce a second remote-core mechanism.
- Boot-gate hard-fail/fallback semantics unchanged: the panel only surfaces
  connection state.
- Env-var attach path (OPENHUMAN_CORE_REUSE_EXISTING) left as documented
  dev-only; not surfaced in the UI.
- Token stays on the existing localStorage path (audit U3 keychain migration
  noted in-code as a known follow-up).
- Registry + route wiring; 12 new settings.core.* i18n keys translated across
  all 13 locales.
- Vitest coverage for status rendering, the reveal toggle, unreachable state,
  and the persist/dispatch/restart save flow.
@M3gA-Mind M3gA-Mind requested a review from a team July 2, 2026 10:51
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new "Core connection" Settings panel allowing users to toggle between local and remote (cloud) core modes, enter a remote URL/token, validate and test connectivity, view live status, and save/restart to apply. Wires the panel into settings routing/registry and adds translations for 18 locales plus tests.

Changes

Core Connection Panel

Layer / File(s) Summary
Core URL resolution and live status logic
app/src/components/settings/panels/CoreConnectionPanel.tsx
Adds resolveActiveCoreUrl helper (cloud URL from Redux, local URL via Tauri core_rpc_url), LiveStatus/TestStatus types, component state, and runLiveCheck effect calling testCoreRpcConnection.
Validation, test, and save/restart flow
app/src/components/settings/panels/CoreConnectionPanel.tsx
Adds URL/token validation with HTTP warning, one-shot handleTest, isDirty detection, and handleSave persisting cloud/local config, clearing caches, updating Redux, and restarting the app.
Panel UI and tests
app/src/components/settings/panels/CoreConnectionPanel.tsx, app/src/components/settings/panels/__tests__/CoreConnectionPanel.test.tsx
Renders status indicator, remote toggle, URL/token inputs, test badges, validation errors, and Save button; tests cover local/cloud status, unreachable failures, and remote save flow.
Settings routing and registry wiring
app/src/components/settings/settingsRouteElements.tsx, app/src/components/settings/settingsRouteRegistry.ts
Adds path="core" route rendering the panel and a corresponding SETTINGS_ROUTE_REGISTRY entry with title/description keys, section/navGroup/navOrder, and search keywords.
Translations
app/src/lib/i18n/en.ts, app/src/lib/i18n/{ar,bn,de,es,fr,hi,id,it,ko,pl,pt,ru,zh-CN}.ts
Adds settings.core.* translation strings (title, descriptions, toggle labels, connection statuses, recheck/save/restart notes) across all 18 locale files.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Panel as CoreConnectionPanel
  participant Redux
  participant Tauri
  participant RpcClient as coreRpcClient

  User->>Panel: open Core settings, toggle remote
  Panel->>Panel: validate(url, token)
  User->>Panel: click "Test connection"
  Panel->>RpcClient: testCoreRpcConnection(url, token)
  RpcClient-->>Panel: ok / auth failed / unreachable
  Panel-->>User: show test status badge
  User->>Panel: click "Save"
  Panel->>Redux: update core mode/url/token
  Panel->>Panel: clear caches
  Panel->>Tauri: restartApp()
Loading

Poem

A rabbit hops through settings new,
Local burrow or cloud-core view 🐇☁️
Toggle, test, a status dot glows,
Save the change, then off it goes —
Restart! and reconnect anew.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: a first-class remote-core setting with live status.
Linked Issues check ✅ Passed The PR implements the requested persisted remote-core setting, explicit toggle, live status indicator, and keeps the change aligned with #4396.
Out of Scope Changes check ✅ Passed The diff stays focused on the core-settings panel, routing, persistence, tests, and locale strings needed for the feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0918dbc17

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +322 to +326
<SettingsSwitch
id="core-use-remote"
checked={useRemote}
onCheckedChange={next => {
setUseRemote(next);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep local mode unavailable in web builds

In non-Tauri web builds the boot picker intentionally forces cloud mode because the browser cannot invoke start_core_process, but this new settings toggle is rendered unconditionally. If a web user turns it off and saves, the local branch persists openhuman_core_mode=local; after reload BootCheck enters local mode and fails trying to start a Tauri-only core, stranding the user on the unreachable recovery screen. Disable/hide the local option when !isTauriEnvironment() so web stays cloud-only.

Useful? React with 👍 / 👎.

</label>
<SettingsTextField
id="core-remote-token"
type="text"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mask the persisted remote-core token

When a user already has a remote core configured, this field is prefilled from the persisted coreMode.token and rendered as plain text, so simply opening Settings → Core connection exposes the long-lived OPENHUMAN_CORE_TOKEN during screen sharing or shoulder-surfing. Since that bearer grants access to the remote core, render it hidden by default (for example type="password" with an explicit reveal/replace affordance).

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/settings/panels/CoreConnectionPanel.tsx`:
- Around line 101-140: The connection checks in runLiveCheck and the manual test
flow are calling testCoreRpcConnection without an AbortSignal, which can leave
the UI stuck in checking/testing on slow or unresponsive endpoints. Add
cancellation/timeout handling by creating and passing an AbortSignal to
testCoreRpcConnection in both paths, and make sure the signal is aborted/cleaned
up when the check is superseded, completes, or times out so the status can
recover promptly.
- Around line 362-371: The bearer token field in CoreConnectionPanel is
currently rendered as plain text, which exposes a secret during normal use.
Update the SettingsTextField for the core token to use a masked password-style
input instead of type="text", and keep any reveal behavior separate if you add
one later. Locate the field by the core-remote-token id in CoreConnectionPanel
and adjust its input type accordingly.
- Around line 154-160: Reject URLs with embedded credentials in
CoreConnectionPanel’s RPC URL validation by checking the parsed URL from new
URL(normalized) for username/password before accepting it. If credentials are
present, set a form error using a new i18n key such as
settings.core.urlCredentialsNotAllowed, and keep the token field as the only
supported credential path. Update the URL validation/normalization flow around
the existing URL parsing logic and ensure the active URL description cannot
render credential-bearing URLs. Add the new translation key through useT() in
en.ts and every locale file.
- Around line 223-253: The handleSave flow in CoreConnectionPanel leaves the UI
stuck if restartApp() rejects because saving is never cleared and the user sees
no error. Wrap the restartApp() call in handleSave with error handling, reset
saving in the failure path, and surface a localized message via useT() using the
new settings.core.restartFailed key. Also add that key to all locale files so
the error text is translated consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: edfe7b02-db04-4680-b6ac-181478c084aa

📥 Commits

Reviewing files that changed from the base of the PR and between f979bfa and c0918db.

📒 Files selected for processing (18)
  • app/src/components/settings/panels/CoreConnectionPanel.tsx
  • app/src/components/settings/panels/__tests__/CoreConnectionPanel.test.tsx
  • app/src/components/settings/settingsRouteElements.tsx
  • app/src/components/settings/settingsRouteRegistry.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts

Comment on lines +101 to +140
const runLiveCheck = useCallback(async () => {
const seq = ++checkSeq.current;
setLiveStatus({ kind: 'checking' });
log('runLiveCheck: mode=%s', coreMode.kind);
const resolved = await resolveActiveCoreUrl(coreMode);
if (seq !== checkSeq.current) return; // superseded by a newer check
setActiveUrl(resolved);
if (!resolved) {
setLiveStatus({ kind: 'unreachable', reason: t('settings.about.serverUrlUnavailable') });
return;
}
try {
const response = await testCoreRpcConnection(resolved);
if (seq !== checkSeq.current) return;
if (response.status === 401 || response.status === 403) {
log('runLiveCheck: auth failed (status=%d)', response.status);
setLiveStatus({ kind: 'authFailed' });
return;
}
if (!response.ok) {
log('runLiveCheck: HTTP %d', response.status);
setLiveStatus({ kind: 'unreachable', reason: `HTTP ${response.status}` });
return;
}
// Drain the body so the connection can be reused; a JSON-RPC error body
// on a 200 does not disprove reachability.
try {
await response.json();
} catch {
/* non-JSON body is unusual but still reachable */
}
log('runLiveCheck: connected');
setLiveStatus({ kind: 'connected' });
} catch (err) {
if (seq !== checkSeq.current) return;
const reason = err instanceof Error ? err.message : 'Connection failed';
log('runLiveCheck: errored: %o', err);
setLiveStatus({ kind: 'unreachable', reason });
}
}, [coreMode, t]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add cancellation/timeout to connection checks.

Both live checks and manual tests call testCoreRpcConnection without its supported AbortSignal, so a non-responsive endpoint can leave the status/button stuck in checking/testing until the platform times out.

Proposed fix
+const CONNECTION_TEST_TIMEOUT_MS = 10_000;
+
+async function testWithTimeout(
+  url: string,
+  token?: string
+): Promise<Response> {
+  const controller = new AbortController();
+  const timeoutId = window.setTimeout(() => controller.abort(), CONNECTION_TEST_TIMEOUT_MS);
+  try {
+    return await testCoreRpcConnection(url, token, { signal: controller.signal });
+  } finally {
+    window.clearTimeout(timeoutId);
+  }
+}
+
...
-      const response = await testCoreRpcConnection(resolved);
+      const response = await testWithTimeout(resolved);
...
-      const response = await testCoreRpcConnection(validated.url, validated.token);
+      const response = await testWithTimeout(validated.url, validated.token);

Also applies to: 188-213

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/CoreConnectionPanel.tsx` around lines 101
- 140, The connection checks in runLiveCheck and the manual test flow are
calling testCoreRpcConnection without an AbortSignal, which can leave the UI
stuck in checking/testing on slow or unresponsive endpoints. Add
cancellation/timeout handling by creating and passing an AbortSignal to
testCoreRpcConnection in both paths, and make sure the signal is aborted/cleaned
up when the check is superseded, completes, or times out so the status can
recover promptly.

Comment on lines +154 to +160
try {
const parsed = new URL(normalized);
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
setFormError(t('bootCheck.urlMustStartWith'));
return null;
}
} catch {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject credentials embedded in the RPC URL.

new URL(...) accepts https://user:pass@example.com/rpc; that secret can then be persisted and rendered back in the active URL description. The separate token field should be the only credential path.

Proposed fix
       const parsed = new URL(normalized);
+      if (parsed.username || parsed.password) {
+        setFormError(t('settings.core.urlCredentialsNotAllowed'));
+        return null;
+      }
       if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {

Add the new settings.core.urlCredentialsNotAllowed key to every locale if you use this message. As per coding guidelines, “All user-facing UI text in the React app must go through useT() … add new keys to en.ts and real translations to every locale file.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const parsed = new URL(normalized);
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
setFormError(t('bootCheck.urlMustStartWith'));
return null;
}
} catch {
try {
const parsed = new URL(normalized);
if (parsed.username || parsed.password) {
setFormError(t('settings.core.urlCredentialsNotAllowed'));
return null;
}
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
setFormError(t('bootCheck.urlMustStartWith'));
return null;
}
} catch {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/CoreConnectionPanel.tsx` around lines 154
- 160, Reject URLs with embedded credentials in CoreConnectionPanel’s RPC URL
validation by checking the parsed URL from new URL(normalized) for
username/password before accepting it. If credentials are present, set a form
error using a new i18n key such as settings.core.urlCredentialsNotAllowed, and
keep the token field as the only supported credential path. Update the URL
validation/normalization flow around the existing URL parsing logic and ensure
the active URL description cannot render credential-bearing URLs. Add the new
translation key through useT() in en.ts and every locale file.

Source: Coding guidelines

Comment on lines +223 to +253
const handleSave = async () => {
if (saving) return;
if (useRemote) {
const validated = validate();
if (!validated) return;
log('handleSave: switching to remote core url=%s tokenLen=%d', validated.url, validated.token.length);
setSaving(true);
// NOTE: the bearer is persisted in plain localStorage via storeCoreToken,
// matching the existing cloud-mode picker. A renderer XSS could read it
// (security audit U3). Migrating this to the OS keychain is a known
// follow-up tracked with the rest of cloud-mode token storage; this panel
// intentionally does not block on it (GH-4396 scope decision).
storeRpcUrl(validated.url);
storeCoreToken(validated.token);
storeCoreMode('cloud');
clearCoreRpcUrlCache();
clearCoreRpcTokenCache();
dispatch(setCoreMode({ kind: 'cloud', url: validated.url, token: validated.token }));
} else {
log('handleSave: switching to local core');
setSaving(true);
storeRpcUrl('');
clearStoredCoreToken();
storeCoreMode('local');
clearCoreRpcUrlCache();
clearCoreRpcTokenCache();
dispatch(setCoreMode({ kind: 'local' }));
}
// Restart so BootCheckGate re-runs against the new mode (unchanged
// boot-gate semantics). In dev this is a renderer reload.
await restartApp();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Recover when restart fails.

If restartApp() rejects, saving stays true and the user gets no feedback after settings were already persisted. Catch the failure and re-enable the action or show a localized error.

Proposed fix
-    await restartApp();
+    try {
+      await restartApp();
+    } catch (err) {
+      log('handleSave: restart failed: %o', err);
+      setFormError(t('settings.core.restartFailed'));
+      setSaving(false);
+    }

Add settings.core.restartFailed to all locale files. As per coding guidelines, all user-facing UI text must go through useT() and new keys require locale translations.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSave = async () => {
if (saving) return;
if (useRemote) {
const validated = validate();
if (!validated) return;
log('handleSave: switching to remote core url=%s tokenLen=%d', validated.url, validated.token.length);
setSaving(true);
// NOTE: the bearer is persisted in plain localStorage via storeCoreToken,
// matching the existing cloud-mode picker. A renderer XSS could read it
// (security audit U3). Migrating this to the OS keychain is a known
// follow-up tracked with the rest of cloud-mode token storage; this panel
// intentionally does not block on it (GH-4396 scope decision).
storeRpcUrl(validated.url);
storeCoreToken(validated.token);
storeCoreMode('cloud');
clearCoreRpcUrlCache();
clearCoreRpcTokenCache();
dispatch(setCoreMode({ kind: 'cloud', url: validated.url, token: validated.token }));
} else {
log('handleSave: switching to local core');
setSaving(true);
storeRpcUrl('');
clearStoredCoreToken();
storeCoreMode('local');
clearCoreRpcUrlCache();
clearCoreRpcTokenCache();
dispatch(setCoreMode({ kind: 'local' }));
}
// Restart so BootCheckGate re-runs against the new mode (unchanged
// boot-gate semantics). In dev this is a renderer reload.
await restartApp();
const handleSave = async () => {
if (saving) return;
if (useRemote) {
const validated = validate();
if (!validated) return;
log('handleSave: switching to remote core url=%s tokenLen=%d', validated.url, validated.token.length);
setSaving(true);
// NOTE: the bearer is persisted in plain localStorage via storeCoreToken,
// matching the existing cloud-mode picker. A renderer XSS could read it
// (security audit U3). Migrating this to the OS keychain is a known
// follow-up tracked with the rest of cloud-mode token storage; this panel
// intentionally does not block on it (GH-4396 scope decision).
storeRpcUrl(validated.url);
storeCoreToken(validated.token);
storeCoreMode('cloud');
clearCoreRpcUrlCache();
clearCoreRpcTokenCache();
dispatch(setCoreMode({ kind: 'cloud', url: validated.url, token: validated.token }));
} else {
log('handleSave: switching to local core');
setSaving(true);
storeRpcUrl('');
clearStoredCoreToken();
storeCoreMode('local');
clearCoreRpcUrlCache();
clearCoreRpcTokenCache();
dispatch(setCoreMode({ kind: 'local' }));
}
// Restart so BootCheckGate re-runs against the new mode (unchanged
// boot-gate semantics). In dev this is a renderer reload.
try {
await restartApp();
} catch (err) {
log('handleSave: restart failed: %o', err);
setFormError(t('settings.core.restartFailed'));
setSaving(false);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/CoreConnectionPanel.tsx` around lines 223
- 253, The handleSave flow in CoreConnectionPanel leaves the UI stuck if
restartApp() rejects because saving is never cleared and the user sees no error.
Wrap the restartApp() call in handleSave with error handling, reset saving in
the failure path, and surface a localized message via useT() using the new
settings.core.restartFailed key. Also add that key to all locale files so the
error text is translated consistently.

Source: Coding guidelines

Comment on lines +362 to +371
<SettingsTextField
id="core-remote-token"
type="text"
mono
autoComplete="off"
spellCheck={false}
data-1p-ignore
data-lpignore="true"
placeholder={t('bootCheck.bearerTokenPlaceholder')}
value={token}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Mask the bearer token field.

The core token is a secret, but type="text" exposes it during normal settings use. Prefer password here, with a reveal control only if needed.

Proposed fix
-                type="text"
+                type="password"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<SettingsTextField
id="core-remote-token"
type="text"
mono
autoComplete="off"
spellCheck={false}
data-1p-ignore
data-lpignore="true"
placeholder={t('bootCheck.bearerTokenPlaceholder')}
value={token}
<SettingsTextField
id="core-remote-token"
type="password"
mono
autoComplete="off"
spellCheck={false}
data-1p-ignore
data-lpignore="true"
placeholder={t('bootCheck.bearerTokenPlaceholder')}
value={token}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/CoreConnectionPanel.tsx` around lines 362
- 371, The bearer token field in CoreConnectionPanel is currently rendered as
plain text, which exposes a secret during normal use. Update the
SettingsTextField for the core token to use a masked password-style input
instead of type="text", and keep any reveal behavior separate if you add one
later. Locate the field by the core-remote-token id in CoreConnectionPanel and
adjust its input type accordingly.

- Apply Prettier to the new panel, test, route wiring, and locale files
  (frontend format:check lane was red).
- Suppress the intentional set-state-in-effect lint warning on the live-check
  effect (matches existing convention).
- Add Vitest cases (Test connection ok/auth/unreachable, live auth-rejected +
  non-ok statuses, validation errors, switch-back-to-local save) — raises
  changed-line coverage on CoreConnectionPanel.tsx to ~90% (was ~67%), over
  the 80% diff gate.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Pushed c3fa32c to fix the two red checks.

Frontend Checks (quality, i18n, docs, coverage):

  • Prettier — the panel, its test, settingsRouteElements.tsx, and the 11 edited locale files weren't Prettier-formatted; ran prettier --write on all of them. format:check now passes.
  • ESLint — clean (0 errors). Suppressed one intentional react-hooks/set-state-in-effect warning on the live-check effect, matching existing convention in the codebase.
  • Coverage — changed-line coverage on CoreConnectionPanel.tsx was ~67%, under the 80% diff gate. Added 7 Vitest cases (Test-connection ok/auth/unreachable, live token-rejected + non-ok statuses, validation errors, and the switch-back-to-local save path). Now 131/145 = 90.3% lines, 11/11 tests pass.
  • i18npnpm i18n:check green (0 missing / 0 extra across all 13 locales); typecheck 0 errors.

PR Submission Checklist: filled in the two items that are now honestly verifiable (format:check passes; diff-coverage measured locally at 90.3%) plus the validation-run metadata. 0 unchecked items remain.

No behaviour changed — these are formatting, a lint-annotation, and added tests only. Not merging; that stays with the maintainer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attach mode: first-class remote-core URL/token setting + status indicator (from #2437-A)

2 participants