From 9ca1ff3bc5bc6fb070a30208f10c12642f83f843 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Sun, 14 Jun 2026 18:57:53 +0700 Subject: [PATCH] feat(app): daemon status with Stop/Restart in settings Co-Authored-By: Claude Sonnet 4.6 --- app/src-tauri/src/bridge.rs | 5 +++++ app/src-tauri/src/lib.rs | 1 + app/src/Settings.tsx | 45 +++++++++++++++++++++++++++++++++---- app/src/socketBridge.ts | 12 ++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/app/src-tauri/src/bridge.rs b/app/src-tauri/src/bridge.rs index 9919d1a..5548541 100644 --- a/app/src-tauri/src/bridge.rs +++ b/app/src-tauri/src/bridge.rs @@ -346,3 +346,8 @@ pub async fn set_config( ) -> Result { data_of(state.request(Cmd::SetConfig { default_shell, font_family, font_size, theme, accent }).await.map_err(|e| e.to_string())?) } + +#[tauri::command] +pub async fn shutdown_daemon(state: BridgeState<'_>) -> Result { + data_of(state.request(Cmd::Shutdown).await.map_err(|e| e.to_string())?) +} diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index a80a005..9bf792c 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -55,6 +55,7 @@ pub fn run() { bridge::health, bridge::get_config, bridge::set_config, + bridge::shutdown_daemon, ]) .run(tauri::generate_context!()) .expect("error while running spacesh"); diff --git a/app/src/Settings.tsx b/app/src/Settings.tsx index 7d48945..85f9b10 100644 --- a/app/src/Settings.tsx +++ b/app/src/Settings.tsx @@ -1,6 +1,6 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { COLORS, FONT } from "./theme"; -import { setConfig } from "./socketBridge"; +import { setConfig, shutdownDaemon, restartDaemon } from "./socketBridge"; import type { ConfigView, DaemonHealth } from "./socketBridge"; const FONTS = ["JetBrains Mono", "Menlo", "Monaco", "SF Mono", "Fira Code", "Cascadia Code"]; @@ -56,5 +56,42 @@ export function Settings({ config, health, onClose }: { config: ConfigView; heal ); } -// Placeholder — fleshed out in Task 11. Keep the signature stable. -function DaemonSection(_props: { health: DaemonHealth | null }) { return null; } +function fmtUptime(ms: number): string { + const s = Math.max(0, Math.floor((Date.now() - ms) / 1000)); + if (s < 60) return `${s}s`; + if (s < 3600) return `${Math.floor(s / 60)}m`; + return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`; +} + +function DaemonSection({ health }: { health: DaemonHealth | null }) { + const [confirm, setConfirm] = useState(null); + return ( +
+
Daemon
+
+ {health ? (<> +
version {health.version} · pid {health.pid}
+
uptime {fmtUptime(health.started_at_ms)}
+ ) :
offline
} +
+
+ + +
+ {confirm && ( +
+
+ {confirm === "stop" ? "Stop the daemon? All sessions end." : "Restart the daemon? Sessions end and respawn."} +
+
+ + +
+
+ )} +
+ ); +} diff --git a/app/src/socketBridge.ts b/app/src/socketBridge.ts index 7c93628..5cffde0 100644 --- a/app/src/socketBridge.ts +++ b/app/src/socketBridge.ts @@ -211,3 +211,15 @@ export async function setConfig(patch: Partial { + try { await invoke("shutdown_daemon"); } catch { /* connection drops as the daemon exits — expected */ } +} + +export async function restartDaemon(): Promise { + await shutdownDaemon(); + // Let the old process exit; the next request triggers the bridge's + // ensure_daemon respawn (or launchd KeepAlive) and reconnects. + await new Promise((r) => setTimeout(r, 600)); + try { await getHealth(); } catch { /* reconnect loop will retry */ } +}