Skip to content

Conversation

@Kitenite
Copy link
Collaborator

@Kitenite Kitenite commented Jan 18, 2026

Summary

  • Add confirmation dialog when toggling terminal persistence that prompts user to restart
    • In production: saves setting and restarts app automatically
    • In dev mode: saves setting and shows toast to restart manually
  • Show background sessions in tray and settings UI regardless of persistence setting
  • Remove automatic daemon shutdown on startup so users can manage existing sessions
  • Remove daemon mode caching to read fresh setting from DB

Test plan

  • Toggle terminal persistence in settings → confirm dialog appears
  • Click "Restart now" in production → app restarts with new setting
  • Toggle in dev mode → toast appears saying to restart manually
  • Disable persistence, restart app → existing daemon sessions still visible in tray/settings
  • Can kill sessions from tray and settings even when persistence is disabled

Summary by CodeRabbit

  • New Features

    • App restart action from Settings and an "Apply persistence" flow that confirms and triggers restart (or shows restart instructions in dev).
    • Menu action to open a specific workspace and navigation to /workspace/{workspaceId}.
  • Improvements

    • Terminal session controls now use a host-backed flow for listing and killing sessions.
    • "Daemon sessions" → "Background sessions"; "Terminal Settings…" added to the tray menu.
    • Added confirmation dialogs for killing sessions and persistence changes.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 18, 2026

📝 Walkthrough

Walkthrough

Adds a TRPC restartApp mutation; introduces tryListExistingDaemonSessions() and shifts terminal session listing/killing to a terminal-host client flow; updates tray/menu events and renderer settings UI to use session-driven logic and to confirm persistence changes with environment-aware restart behavior.

Changes

Cohort / File(s) Summary
App Restart Mutation
apps/desktop/src/lib/trpc/routers/settings/index.ts
New restartApp mutation that calls app.relaunch() and quitWithoutConfirmation(); added imports for app and quitWithoutConfirmation.
Terminal Host Client & API
apps/desktop/src/main/lib/terminal/index.ts, apps/desktop/src/main/lib/terminal-host/*
Added tryListExistingDaemonSessions() and type import for ListSessionsResponse; daemon checks now attempt host connection and return { daemonRunning, sessions } or false/empty on failure.
Terminal Router (TRPC)
apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
Session listing and kill flows moved from terminal.management to getTerminalHostClient() + tryConnectAndAuthenticate(); uses client.listSessions/killAll/kill with graceful fallback when disconnected.
Tray Integration
apps/desktop/src/main/lib/tray/index.ts
Tray menu now uses tryListExistingDaemonSessions() and daemonRunning; restartDaemon uses host-client shutdownIfRunning({ killSessions: true }); added terminal settings submenu entry; param rename daemonEnableddaemonRunning.
Settings UI (Renderer)
apps/desktop/src/renderer/routes/_authenticated/settings/terminal/page.tsx
Replaced daemon-mode flag usage with session-driven checks; added pendingPersistenceChange, confirmation dialog, env-aware post-success (dev toast, prod restartApp mutation), and adjusted session/action enablement and dialogs.
Menu Events & Router
apps/desktop/src/main/lib/menu-events.ts, apps/desktop/src/lib/trpc/routers/menu.ts
Added "terminal" to SettingsSection; introduced OpenWorkspaceEvent type and extended MenuEvent union with open-workspace plus handler wiring.
Renderer Navigation
apps/desktop/src/renderer/routes/_authenticated/layout.tsx
Added handling of open-workspace menu event to navigate to /workspace/{workspaceId}.
Tests / Mocks
apps/desktop/src/lib/trpc/routers/terminal/terminal.stream.test.ts
Added mocks for main/lib/terminal (tryListExistingDaemonSessions) and main/lib/terminal-host/client (getTerminalHostClient) to bypass Electron in tests.
Minor Startup Comment
apps/desktop/src/main/index.ts
Removed an explanatory inline comment before shutdownOrphanedDaemon(); no behavior change.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Settings UI
    participant TRPC as TRPC Server
    participant Main as Desktop Main
    participant TerminalHost as Terminal Host Daemon
    participant App as Electron App

    User->>UI: Confirm persistence change
    UI->>TRPC: confirmPersistenceChange mutation
    TRPC->>Main: update persistence setting

    alt production
        UI->>TRPC: restartApp mutation
        TRPC->>Main: restart request
        Main->>App: app.relaunch()
        Main->>App: quitWithoutConfirmation()
        App->>App: Restart
    else development
        TRPC-->>UI: return success (show toast)
    end

    Note over UI,TerminalHost: Terminal session operations
    UI->>TRPC: listDaemonSessions / killSession requests
    TRPC->>TerminalHost: getTerminalHostClient() -> tryConnectAndAuthenticate()
    alt connected
        TerminalHost-->>TRPC: listSessions / kill success
        TRPC-->>UI: return sessions / success
    else disconnected
        TerminalHost-->>TRPC: respond daemonRunning=false / empty sessions
        TRPC-->>UI: return disabled/empty result
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • AviPeltz

Poem

🐰 I nudged the daemon with a tiny hop,
sessions hush, then start or stop,
a click to restart — prod leaps away,
devs get a toast to save the day,
the rabbit hops, the app will play. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the two main changes: adding a restart prompt for terminal persistence toggle and showing sessions regardless of mode.
Description check ✅ Passed The PR description covers the main objectives but lacks details in the template sections like Related Issues, Type of Change details, and comprehensive Testing steps.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

… show sessions regardless of mode

- Add confirmation dialog when toggling terminal persistence that restarts app (production) or shows toast (dev mode)
- Show background sessions in tray and settings regardless of persistence setting
- Remove automatic daemon shutdown on startup to allow managing existing sessions
- Remove daemon mode caching to read fresh from DB
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/lib/trpc/routers/terminal/terminal.ts (1)

301-307: Track killed sessions by paneId to prevent unintended reattach.
userKilledSessions is checked against paneId in createOrAttach, but these paths add sessionId. If they diverge, killed sessions can resurrect. Track by paneId while still killing by sessionId.

🐛 Suggested fix
-			const beforeIds = before.sessions.map((s) => s.sessionId);
-			for (const id of beforeIds) {
-				userKilledSessions.add(id);
-			}
+			const beforeIds = before.sessions.map((s) => s.sessionId);
+			for (const session of before.sessions) {
+				userKilledSessions.add(session.paneId);
+			}

...

-				for (const session of toKill) {
-					userKilledSessions.add(session.sessionId);
-					await client.kill({ sessionId: session.sessionId });
-				}
+				for (const session of toKill) {
+					userKilledSessions.add(session.paneId);
+					await client.kill({ sessionId: session.sessionId });
+				}

Also applies to: 362-369

🤖 Fix all issues with AI agents
In `@apps/desktop/src/main/lib/terminal/index.ts`:
- Around line 136-156: In tryListExistingDaemonSessions, don't swallow errors
when DEBUG_TERMINAL is false: inside the catch block (around
getTerminalHostClient/tryConnectAndAuthenticate/listSessions calls) always emit
a warning-level log that includes the error details and context (e.g.,
"[TerminalManager] Failed to list existing daemon sessions") using the existing
logger or console.warn if no logger is available, while still returning {
daemonRunning: false, sessions: [] }; ensure DEBUG_TERMINAL controls the more
verbose debug console.log but a warning is always emitted for observability.

In `@apps/desktop/src/main/lib/tray/index.ts`:
- Around line 130-136: The tray killSession function is passing the pane
identifier incorrectly to the host client's kill method; update killSession (and
any callers) so client.kill is invoked with an object containing sessionId
(e.g., client.kill({ sessionId: paneId })) rather than passing paneId directly;
locate the killSession function and the getTerminalHostClient / client.kill
usage to make this change so the host receives the correct sessionId field.
🧹 Nitpick comments (1)
apps/desktop/src/main/lib/tray/index.ts (1)

166-169: Prefer params object for multi-arg helpers.
This helper now has 2 positional args; switching to a params object aligns with repo conventions. As per coding guidelines, use an object parameter for 2+ arguments.

♻️ Suggested refactor
-function buildSessionsSubmenu(
-	sessions: ListSessionsResponse["sessions"],
-	daemonRunning: boolean,
-): MenuItemConstructorOptions[] {
+function buildSessionsSubmenu({
+	sessions,
+	daemonRunning,
+}: {
+	sessions: ListSessionsResponse["sessions"];
+	daemonRunning: boolean;
+}): MenuItemConstructorOptions[] {
-	const sessionsSubmenu = buildSessionsSubmenu(sessions, daemonRunning);
+	const sessionsSubmenu = buildSessionsSubmenu({ sessions, daemonRunning });

Also applies to: 249-250

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/routes/_authenticated/settings/terminal/page.tsx (1)

56-111: Handle restart failures for user feedback.

restartApp mutation errors are currently silent, which can leave users stuck if the relaunch fails. Add an onError toast (or similar) so failures are visible. As per coding guidelines, avoid silent failures.

🔧 Suggested fix
-const restartApp = electronTrpc.settings.restartApp.useMutation();
+const restartApp = electronTrpc.settings.restartApp.useMutation({
+	onError: (error) => {
+		toast.error("Failed to restart app", {
+			description: error.message,
+		});
+	},
+});

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/main/lib/tray/index.ts (1)

138-151: Use session.sessionId instead of session.paneId when killing sessions.

The killSession function receives paneId as a parameter but passes it to client.kill() which expects sessionId. Since ListSessionsResponse contains distinct sessionId and paneId fields (see apps/desktop/src/main/lib/terminal-host/types.ts lines 233-235), this mismatch causes kill operations to target the wrong session.

Change line 138 to accept sessionId: string as the parameter, and line 213 to pass session.sessionId instead of session.paneId. This matches the correct usage pattern in apps/desktop/src/lib/trpc/routers/terminal/terminal.ts line 369.

🧹 Nitpick comments (1)
apps/desktop/src/main/lib/tray/index.ts (1)

181-231: "Terminal Settings..." is only visible when there are active sessions.

The menu item at lines 223-226 is inside the else block, so it only appears when sessions exist. Users with no active sessions cannot access Terminal Settings from this submenu, while "Restart Daemon" is always visible (line 233).

Consider moving "Terminal Settings..." outside the conditional block for consistent access:

♻️ Suggested fix
 	if (aliveSessions.length === 0) {
 		menuItems.push({ label: "No active sessions", enabled: false });
-	} else {
+	}
+
+	if (aliveSessions.length > 0) {
 		const byWorkspace = new Map<string, ListSessionsResponse["sessions"]>();
 		// ... workspace grouping logic ...

 		menuItems.push({ type: "separator" });
-		menuItems.push({
-			label: "Terminal Settings...",
-			click: openTerminalSettings,
-		});
 		menuItems.push({
 			label: "Kill All Sessions",
 			click: killAllSessions,
 		});
 	}

+	menuItems.push({
+		label: "Terminal Settings...",
+		click: openTerminalSettings,
+	});
 	menuItems.push({
 		label: "Restart Daemon",
 		enabled: daemonRunning,
 		click: restartDaemon,
 	});

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/main/lib/tray/index.ts (1)

133-146: Fix the sessionId mismatch in the killSession function—pass session.sessionId instead of session.paneId.

The Terminal Host daemon stores sessions by sessionId and the kill() method uses sessionId to look up the session. However, the function currently passes paneId as the sessionId parameter to client.kill(), which will fail to find and kill the session. At line 208, change the call to killSession(session.sessionId).

@Kitenite Kitenite merged commit db97ab7 into main Jan 19, 2026
4 of 5 checks passed
@Kitenite Kitenite deleted the switch-restart-app branch January 19, 2026 00:27
@github-actions
Copy link

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/desktop/src/main/lib/tray/index.ts`:
- Around line 115-121: In killAllSessions (and similarly in killSession) add a
diagnostic log when tryConnectAndAuthenticate() returns false so the function
doesn't silently no-op: after calling const client = getTerminalHostClient() and
awaiting client.tryConnectAndAuthenticate(), if connected is false call
processLogger.warn or console.warn with a clear message (e.g., "[Tray] Terminal
host unavailable, cannot kill all sessions" and include any identifying context)
and then return; keep the existing success log path unchanged and reuse the same
pattern in killSession to mirror behavior.
♻️ Duplicate comments (1)
apps/desktop/src/main/lib/tray/index.ts (1)

130-136: Use sessionId instead of paneId when killing sessions.
client.kill expects sessionId, but the tray path still passes paneId, which can break kills when IDs differ.

🐛 Proposed fix
-async function killSession(paneId: string): Promise<void> {
+async function killSession(sessionId: string): Promise<void> {
 	try {
 		const client = getTerminalHostClient();
 		const connected = await client.tryConnectAndAuthenticate();
 		if (connected) {
-			await client.kill({ sessionId: paneId });
-			console.log(`[Tray] Killed session: ${paneId}`);
+			await client.kill({ sessionId });
+			console.log(`[Tray] Killed session: ${sessionId}`);
 		}
 	} catch (error) {
-		console.error(`[Tray] Failed to kill session ${paneId}:`, error);
+		console.error(`[Tray] Failed to kill session ${sessionId}:`, error);
 	}
-							click: () => killSession(session.paneId),
+							click: () => killSession(session.sessionId),

Also applies to: 205-206

🧹 Nitpick comments (2)
apps/desktop/src/lib/trpc/routers/terminal/terminal.stream.test.ts (1)

91-99: LGTM!

The mock provides minimal stubs that isolate the tests from Electron dependencies. The default return values (false for auth, empty sessions, empty objects for kill operations) are sensible defaults for test isolation.

Consider adding explicit return types to the mock methods if the actual TerminalHostClient interface evolves, to catch mismatches at compile time. This is optional given this is test scaffolding.

apps/desktop/src/main/lib/tray/index.ts (1)

166-169: Use an object param for buildSessionsSubmenu to avoid boolean blindness.
Two positional params (especially a boolean) are discouraged by the repo guidelines. As per coding guidelines, please switch to an object parameter.

♻️ Suggested refactor
-function buildSessionsSubmenu(
-	sessions: ListSessionsResponse["sessions"],
-	daemonRunning: boolean,
-): MenuItemConstructorOptions[] {
+function buildSessionsSubmenu({
+	sessions,
+	daemonRunning,
+}: {
+	sessions: ListSessionsResponse["sessions"];
+	daemonRunning: boolean;
+}): MenuItemConstructorOptions[] {
-	const sessionsSubmenu = buildSessionsSubmenu(sessions, daemonRunning);
+	const sessionsSubmenu = buildSessionsSubmenu({ sessions, daemonRunning });

Also applies to: 254-255

Comment on lines 115 to 121
async function killAllSessions(): Promise<void> {
try {
const manager = getActiveTerminalManager();
if (manager instanceof DaemonTerminalManager) {
await manager.forceKillAll();
const client = getTerminalHostClient();
const connected = await client.tryConnectAndAuthenticate();
if (connected) {
await client.killAll({});
console.log("[Tray] Killed all daemon sessions");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Log when terminal host is unavailable to avoid silent no-ops.
Right now, if tryConnectAndAuthenticate() returns false, the action silently does nothing. Consider logging (and apply the same pattern in killSession) so failures are diagnosable.

🔧 Suggested tweak
-		const connected = await client.tryConnectAndAuthenticate();
-		if (connected) {
-			await client.killAll({});
-			console.log("[Tray] Killed all daemon sessions");
-		}
+		const connected = await client.tryConnectAndAuthenticate();
+		if (!connected) {
+			console.warn("[Tray/killAllSessions] Terminal host unavailable; no sessions killed");
+		} else {
+			await client.killAll({});
+			console.log("[Tray] Killed all daemon sessions");
+		}
📝 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
async function killAllSessions(): Promise<void> {
try {
const manager = getActiveTerminalManager();
if (manager instanceof DaemonTerminalManager) {
await manager.forceKillAll();
const client = getTerminalHostClient();
const connected = await client.tryConnectAndAuthenticate();
if (connected) {
await client.killAll({});
console.log("[Tray] Killed all daemon sessions");
async function killAllSessions(): Promise<void> {
try {
const client = getTerminalHostClient();
const connected = await client.tryConnectAndAuthenticate();
if (!connected) {
console.warn("[Tray/killAllSessions] Terminal host unavailable; no sessions killed");
} else {
await client.killAll({});
console.log("[Tray] Killed all daemon sessions");
}
} catch (error) {
console.error("[Tray] Failed to kill all sessions:", error);
}
}
🤖 Prompt for AI Agents
In `@apps/desktop/src/main/lib/tray/index.ts` around lines 115 - 121, In
killAllSessions (and similarly in killSession) add a diagnostic log when
tryConnectAndAuthenticate() returns false so the function doesn't silently
no-op: after calling const client = getTerminalHostClient() and awaiting
client.tryConnectAndAuthenticate(), if connected is false call
processLogger.warn or console.warn with a clear message (e.g., "[Tray] Terminal
host unavailable, cannot kill all sessions" and include any identifying context)
and then return; keep the existing success log path unchanged and reuse the same
pattern in killSession to mirror behavior.

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.

2 participants