From 2f121f8514e4b87b85ce15ab6bfaf963712cfc32 Mon Sep 17 00:00:00 2001 From: slkzgm Date: Thu, 15 Jan 2026 18:05:47 +0100 Subject: [PATCH] feat(settings): add remote backend configuration --- src-tauri/src/types.rs | 31 +++++- .../settings/components/SettingsView.tsx | 101 ++++++++++++++++++ src/features/settings/hooks/useAppSettings.ts | 3 + src/types.ts | 4 + 4 files changed, 138 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index e35e80701..42a6d8692 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -128,6 +128,12 @@ pub(crate) struct WorkspaceSettings { pub(crate) struct AppSettings { #[serde(default, rename = "codexBin")] pub(crate) codex_bin: Option, + #[serde(default, rename = "backendMode")] + pub(crate) backend_mode: BackendMode, + #[serde(default = "default_remote_backend_host", rename = "remoteBackendHost")] + pub(crate) remote_backend_host: String, + #[serde(default, rename = "remoteBackendToken")] + pub(crate) remote_backend_token: Option, #[serde(default = "default_access_mode", rename = "defaultAccessMode")] pub(crate) default_access_mode: String, #[serde(default = "default_ui_scale", rename = "uiScale")] @@ -144,10 +150,27 @@ pub(crate) struct AppSettings { pub(crate) experimental_steer_enabled: bool, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "lowercase")] +pub(crate) enum BackendMode { + Local, + Remote, +} + +impl Default for BackendMode { + fn default() -> Self { + BackendMode::Local + } +} + fn default_access_mode() -> String { "current".to_string() } +fn default_remote_backend_host() -> String { + "127.0.0.1:4732".to_string() +} + fn default_ui_scale() -> f64 { 1.0 } @@ -164,6 +187,9 @@ impl Default for AppSettings { fn default() -> Self { Self { codex_bin: None, + backend_mode: BackendMode::Local, + remote_backend_host: default_remote_backend_host(), + remote_backend_token: None, default_access_mode: "current".to_string(), ui_scale: 1.0, notification_sounds_enabled: true, @@ -174,12 +200,15 @@ impl Default for AppSettings { #[cfg(test)] mod tests { - use super::{AppSettings, WorkspaceEntry, WorkspaceKind}; + use super::{AppSettings, BackendMode, WorkspaceEntry, WorkspaceKind}; #[test] fn app_settings_defaults_from_empty_json() { let settings: AppSettings = serde_json::from_str("{}").expect("settings deserialize"); assert!(settings.codex_bin.is_none()); + assert!(matches!(settings.backend_mode, BackendMode::Local)); + assert_eq!(settings.remote_backend_host, "127.0.0.1:4732"); + assert!(settings.remote_backend_token.is_none()); assert_eq!(settings.default_access_mode, "current"); assert!((settings.ui_scale - 1.0).abs() < f64::EPSILON); assert!(settings.notification_sounds_enabled); diff --git a/src/features/settings/components/SettingsView.tsx b/src/features/settings/components/SettingsView.tsx index d810efc89..ad0487016 100644 --- a/src/features/settings/components/SettingsView.tsx +++ b/src/features/settings/components/SettingsView.tsx @@ -57,6 +57,8 @@ export function SettingsView({ }: SettingsViewProps) { const [activeSection, setActiveSection] = useState("projects"); const [codexPathDraft, setCodexPathDraft] = useState(appSettings.codexBin ?? ""); + const [remoteHostDraft, setRemoteHostDraft] = useState(appSettings.remoteBackendHost); + const [remoteTokenDraft, setRemoteTokenDraft] = useState(appSettings.remoteBackendToken ?? ""); const [scaleDraft, setScaleDraft] = useState( `${Math.round(clampUiScale(appSettings.uiScale) * 100)}%`, ); @@ -84,6 +86,14 @@ export function SettingsView({ setCodexPathDraft(appSettings.codexBin ?? ""); }, [appSettings.codexBin]); + useEffect(() => { + setRemoteHostDraft(appSettings.remoteBackendHost); + }, [appSettings.remoteBackendHost]); + + useEffect(() => { + setRemoteTokenDraft(appSettings.remoteBackendToken ?? ""); + }, [appSettings.remoteBackendToken]); + useEffect(() => { setScaleDraft(`${Math.round(clampUiScale(appSettings.uiScale) * 100)}%`); }, [appSettings.uiScale]); @@ -120,6 +130,30 @@ export function SettingsView({ } }; + const handleCommitRemoteHost = async () => { + const nextHost = remoteHostDraft.trim() || "127.0.0.1:4732"; + setRemoteHostDraft(nextHost); + if (nextHost === appSettings.remoteBackendHost) { + return; + } + await onUpdateAppSettings({ + ...appSettings, + remoteBackendHost: nextHost, + }); + }; + + const handleCommitRemoteToken = async () => { + const nextToken = remoteTokenDraft.trim() ? remoteTokenDraft.trim() : null; + setRemoteTokenDraft(nextToken ?? ""); + if (nextToken === appSettings.remoteBackendToken) { + return; + } + await onUpdateAppSettings({ + ...appSettings, + remoteBackendToken: nextToken, + }); + }; + const handleCommitScale = async () => { if (parsedScale === null) { setScaleDraft(`${Math.round(clampUiScale(appSettings.uiScale) * 100)}%`); @@ -494,6 +528,73 @@ export function SettingsView({ +
+ + +
+ Remote mode connects to a separate daemon running the backend on another machine (e.g. WSL2/Linux). +
+
+ + {appSettings.backendMode === "remote" && ( +
+
Remote backend
+
+ setRemoteHostDraft(event.target.value)} + onBlur={() => { + void handleCommitRemoteHost(); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + void handleCommitRemoteHost(); + } + }} + aria-label="Remote backend host" + /> + setRemoteTokenDraft(event.target.value)} + onBlur={() => { + void handleCommitRemoteToken(); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + void handleCommitRemoteToken(); + } + }} + aria-label="Remote backend token" + /> +
+
+ Start the daemon separately and point CodexMonitor to it (host:port + token). +
+
+ )} +
Workspace overrides
diff --git a/src/features/settings/hooks/useAppSettings.ts b/src/features/settings/hooks/useAppSettings.ts index e431ea9a5..b52bb2e4f 100644 --- a/src/features/settings/hooks/useAppSettings.ts +++ b/src/features/settings/hooks/useAppSettings.ts @@ -5,6 +5,9 @@ import { clampUiScale, UI_SCALE_DEFAULT } from "../../../utils/uiScale"; const defaultSettings: AppSettings = { codexBin: null, + backendMode: "local", + remoteBackendHost: "127.0.0.1:4732", + remoteBackendToken: null, defaultAccessMode: "current", uiScale: UI_SCALE_DEFAULT, notificationSoundsEnabled: true, diff --git a/src/types.ts b/src/types.ts index f725411d4..a5c1a99ff 100644 --- a/src/types.ts +++ b/src/types.ts @@ -61,9 +61,13 @@ export type ReviewTarget = | { type: "custom"; instructions: string }; export type AccessMode = "read-only" | "current" | "full-access"; +export type BackendMode = "local" | "remote"; export type AppSettings = { codexBin: string | null; + backendMode: BackendMode; + remoteBackendHost: string; + remoteBackendToken: string | null; defaultAccessMode: AccessMode; uiScale: number; notificationSoundsEnabled: boolean;