Skip to content

Commit 0aa029a

Browse files
committed
🤖 Add useNewWorkspaceOptions hook for consistent runtime management
- Create useNewWorkspaceOptions hook to manage runtime preferences with localStorage - Refactor NewWorkspaceModal to use the hook instead of direct state management - Update /new command to use saved runtime preference when -r flag not provided - Eliminate duplication between modal and command-line workspace creation Hook provides: - Automatic loading of saved runtime preferences - Consistent state management (runtimeMode + sshHost) - Helper method getRuntimeString() for IPC calls - Single source of truth for runtime preference logic Now both NewWorkspaceModal and /new command respect saved preferences, ensuring consistent behavior across all workspace creation paths.
1 parent 9deae1b commit 0aa029a

File tree

3 files changed

+104
-31
lines changed

3 files changed

+104
-31
lines changed

src/components/NewWorkspaceModal.tsx

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useEffect, useId, useState } from "react";
22
import { Modal, ModalInfo, ModalActions, CancelButton, PrimaryButton } from "./Modal";
33
import { TooltipWrapper, Tooltip } from "./Tooltip";
44
import { formatNewCommand } from "@/utils/chatCommands";
5-
import { getRuntimeKey } from "@/constants/storage";
5+
import { useNewWorkspaceOptions } from "@/hooks/useNewWorkspaceOptions";
66

77
interface NewWorkspaceModalProps {
88
isOpen: boolean;
@@ -31,13 +31,15 @@ const NewWorkspaceModal: React.FC<NewWorkspaceModalProps> = ({
3131
}) => {
3232
const [branchName, setBranchName] = useState("");
3333
const [trunkBranch, setTrunkBranch] = useState(defaultTrunkBranch ?? branches[0] ?? "");
34-
const [runtimeMode, setRuntimeMode] = useState<"local" | "ssh">("local");
35-
const [sshHost, setSshHost] = useState("");
3634
const [isLoading, setIsLoading] = useState(false);
3735
const [error, setError] = useState<string | null>(null);
3836
const infoId = useId();
3937
const hasBranches = branches.length > 0;
4038

39+
// Load runtime preferences from localStorage for this project
40+
const [runtimeOptions, setRuntimeOptions] = useNewWorkspaceOptions(projectPath, isOpen);
41+
const { runtimeMode, sshHost, getRuntimeString } = runtimeOptions;
42+
4143
useEffect(() => {
4244
setError(loadErrorMessage ?? null);
4345
}, [loadErrorMessage]);
@@ -59,30 +61,10 @@ const NewWorkspaceModal: React.FC<NewWorkspaceModalProps> = ({
5961
});
6062
}, [branches, defaultTrunkBranch, hasBranches]);
6163

62-
// Load saved runtime preference when modal opens
63-
useEffect(() => {
64-
if (isOpen && projectPath) {
65-
const runtimeKey = getRuntimeKey(projectPath);
66-
const savedRuntime = localStorage.getItem(runtimeKey);
67-
if (savedRuntime) {
68-
// Parse the saved runtime string (format: "ssh <host>" or undefined for local)
69-
if (savedRuntime.startsWith("ssh ")) {
70-
const host = savedRuntime.substring(4).trim();
71-
setRuntimeMode("ssh");
72-
setSshHost(host);
73-
} else {
74-
setRuntimeMode("local");
75-
setSshHost("");
76-
}
77-
}
78-
}
79-
}, [isOpen, projectPath]);
80-
8164
const handleCancel = () => {
8265
setBranchName("");
8366
setTrunkBranch(defaultTrunkBranch ?? branches[0] ?? "");
84-
setRuntimeMode("local");
85-
setSshHost("");
67+
setRuntimeOptions("local", "");
8668
setError(loadErrorMessage ?? null);
8769
onClose();
8870
};
@@ -119,14 +101,13 @@ const NewWorkspaceModal: React.FC<NewWorkspaceModalProps> = ({
119101
setError(null);
120102

121103
try {
122-
// Build runtime string if SSH selected
123-
const runtime = runtimeMode === "ssh" ? `ssh ${sshHost.trim()}` : undefined;
104+
// Get runtime string from hook helper
105+
const runtime = getRuntimeString();
124106

125107
await onAdd(trimmedBranchName, normalizedTrunkBranch, runtime);
126108
setBranchName("");
127109
setTrunkBranch(defaultTrunkBranch ?? branches[0] ?? "");
128-
setRuntimeMode("local");
129-
setSshHost("");
110+
setRuntimeOptions("local", "");
130111
onClose();
131112
} catch (err) {
132113
const message = err instanceof Error ? err.message : "Failed to create workspace";
@@ -225,7 +206,8 @@ const NewWorkspaceModal: React.FC<NewWorkspaceModalProps> = ({
225206
id="runtimeMode"
226207
value={runtimeMode}
227208
onChange={(event) => {
228-
setRuntimeMode(event.target.value as "local" | "ssh");
209+
const newMode = event.target.value as "local" | "ssh";
210+
setRuntimeOptions(newMode, newMode === "local" ? "" : sshHost);
229211
setError(null);
230212
}}
231213
disabled={isLoading}
@@ -243,7 +225,7 @@ const NewWorkspaceModal: React.FC<NewWorkspaceModalProps> = ({
243225
type="text"
244226
value={sshHost}
245227
onChange={(event) => {
246-
setSshHost(event.target.value);
228+
setRuntimeOptions("ssh", event.target.value);
247229
setError(null);
248230
}}
249231
placeholder="hostname or user@hostname"
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { useState, useEffect } from "react";
2+
import { getRuntimeKey } from "@/constants/storage";
3+
4+
export interface WorkspaceRuntimeOptions {
5+
runtimeMode: "local" | "ssh";
6+
sshHost: string;
7+
/**
8+
* Returns the runtime string for IPC calls (format: "ssh <host>" or undefined for local)
9+
*/
10+
getRuntimeString: () => string | undefined;
11+
}
12+
13+
/**
14+
* Hook to manage workspace creation runtime options with localStorage persistence.
15+
* Loads saved runtime preference for a project and provides consistent state management.
16+
*
17+
* @param projectPath - Path to the project (used as key for localStorage)
18+
* @param enabled - Whether to load saved preferences (default: true)
19+
* @returns Runtime options state and setter
20+
*/
21+
export function useNewWorkspaceOptions(
22+
projectPath: string | null | undefined,
23+
enabled = true
24+
): [WorkspaceRuntimeOptions, (mode: "local" | "ssh", host?: string) => void] {
25+
const [runtimeMode, setRuntimeMode] = useState<"local" | "ssh">("local");
26+
const [sshHost, setSshHost] = useState("");
27+
28+
// Load saved runtime preference when projectPath changes
29+
useEffect(() => {
30+
if (!enabled || !projectPath) {
31+
// Reset to defaults when disabled or no project
32+
setRuntimeMode("local");
33+
setSshHost("");
34+
return;
35+
}
36+
37+
const runtimeKey = getRuntimeKey(projectPath);
38+
const savedRuntime = localStorage.getItem(runtimeKey);
39+
40+
if (savedRuntime) {
41+
// Parse the saved runtime string (format: "ssh <host>" or undefined for local)
42+
if (savedRuntime.startsWith("ssh ")) {
43+
const host = savedRuntime.substring(4).trim();
44+
setRuntimeMode("ssh");
45+
setSshHost(host);
46+
} else {
47+
setRuntimeMode("local");
48+
setSshHost("");
49+
}
50+
} else {
51+
// No saved preference, use defaults
52+
setRuntimeMode("local");
53+
setSshHost("");
54+
}
55+
}, [projectPath, enabled]);
56+
57+
// Setter for updating both mode and host
58+
const setRuntimeOptions = (mode: "local" | "ssh", host?: string) => {
59+
setRuntimeMode(mode);
60+
setSshHost(host ?? "");
61+
};
62+
63+
// Helper to get runtime string for IPC calls
64+
const getRuntimeString = (): string | undefined => {
65+
if (runtimeMode === "ssh") {
66+
const trimmedHost = sshHost.trim();
67+
return trimmedHost ? `ssh ${trimmedHost}` : undefined;
68+
}
69+
return undefined;
70+
};
71+
72+
return [
73+
{
74+
runtimeMode,
75+
sshHost,
76+
getRuntimeString,
77+
},
78+
setRuntimeOptions,
79+
];
80+
}

src/utils/chatCommands.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type { Toast } from "@/components/ChatInputToast";
1515
import type { ParsedCommand } from "@/utils/slashCommands/types";
1616
import { applyCompactionOverrides } from "@/utils/messages/compactionOptions";
1717
import { resolveCompactionModel } from "@/utils/messages/compactionModelPreference";
18+
import { getRuntimeKey } from "@/constants/storage";
1819

1920
// ============================================================================
2021
// Workspace Creation
@@ -92,8 +93,18 @@ export async function createNewWorkspace(
9293
effectiveTrunk = recommendedTrunk ?? "main";
9394
}
9495

96+
// Use saved runtime preference if not explicitly provided
97+
let effectiveRuntime = options.runtime;
98+
if (effectiveRuntime === undefined) {
99+
const runtimeKey = getRuntimeKey(options.projectPath);
100+
const savedRuntime = localStorage.getItem(runtimeKey);
101+
if (savedRuntime) {
102+
effectiveRuntime = savedRuntime;
103+
}
104+
}
105+
95106
// Parse runtime config if provided
96-
const runtimeConfig = parseRuntimeString(options.runtime, options.workspaceName);
107+
const runtimeConfig = parseRuntimeString(effectiveRuntime, options.workspaceName);
97108

98109
const result = await window.api.workspace.create(
99110
options.projectPath,

0 commit comments

Comments
 (0)