Skip to content

Commit c829e4f

Browse files
committed
feat: add fetch-latest option for workspace creation
1 parent 816297d commit c829e4f

File tree

16 files changed

+338
-23
lines changed

16 files changed

+338
-23
lines changed

src/browser/api.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,15 @@ const webApi: IPCApi = {
211211
},
212212
workspace: {
213213
list: () => invokeIPC(IPC_CHANNELS.WORKSPACE_LIST),
214-
create: (projectPath, branchName, trunkBranch) =>
215-
invokeIPC(IPC_CHANNELS.WORKSPACE_CREATE, projectPath, branchName, trunkBranch),
214+
create: (projectPath, branchName, trunkBranch, runtimeConfig, options) =>
215+
invokeIPC(
216+
IPC_CHANNELS.WORKSPACE_CREATE,
217+
projectPath,
218+
branchName,
219+
trunkBranch,
220+
runtimeConfig,
221+
options
222+
),
216223
remove: (workspaceId, options) =>
217224
invokeIPC(IPC_CHANNELS.WORKSPACE_REMOVE, workspaceId, options),
218225
rename: (workspaceId, newName) =>

src/browser/components/ChatInput/CreationControls.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ interface CreationControlsProps {
77
branches: string[];
88
trunkBranch: string;
99
onTrunkBranchChange: (branch: string) => void;
10+
fetchLatest: boolean;
11+
onFetchLatestChange: (value: boolean) => void;
1012
runtimeMode: RuntimeMode;
1113
sshHost: string;
1214
onRuntimeChange: (mode: RuntimeMode, host: string) => void;
@@ -74,6 +76,30 @@ export function CreationControls(props: CreationControlsProps) {
7476
</Tooltip>
7577
</TooltipWrapper>
7678
</div>
79+
80+
{/* Fetch latest option */}
81+
<div className="flex items-center gap-1" data-component="FetchLatestGroup">
82+
<label className="flex items-center gap-1 text-xs text-muted" htmlFor="fetch-latest">
83+
<input
84+
id="fetch-latest"
85+
type="checkbox"
86+
className="accent-accent h-3.5 w-3.5"
87+
checked={props.fetchLatest}
88+
onChange={(event) => props.onFetchLatestChange(event.target.checked)}
89+
disabled={props.disabled}
90+
/>
91+
Fetch latest
92+
</label>
93+
<TooltipWrapper inline>
94+
<span className="text-muted cursor-help text-xs">?</span>
95+
<Tooltip className="tooltip" align="center" width="wide">
96+
<strong>Fetch latest:</strong>
97+
<br />
98+
Runs <code>git fetch origin --prune</code> before branching so new workspaces start from
99+
the freshest remote state.
100+
</Tooltip>
101+
</TooltipWrapper>
102+
</div>
77103
</div>
78104
);
79105
}

src/browser/components/ChatInput/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,8 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
993993
branches={creationState.branches}
994994
trunkBranch={creationState.trunkBranch}
995995
onTrunkBranchChange={creationState.setTrunkBranch}
996+
fetchLatest={creationState.fetchLatest}
997+
onFetchLatestChange={creationState.setFetchLatest}
996998
runtimeMode={creationState.runtimeMode}
997999
sshHost={creationState.sshHost}
9981000
onRuntimeChange={creationState.setRuntimeOptions}

src/browser/components/ChatInput/useCreationWorkspace.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ interface UseCreationWorkspaceReturn {
3636
branches: string[];
3737
trunkBranch: string;
3838
setTrunkBranch: (branch: string) => void;
39+
fetchLatest: boolean;
40+
setFetchLatest: (value: boolean) => void;
3941
runtimeMode: RuntimeMode;
4042
sshHost: string;
4143
setRuntimeOptions: (mode: RuntimeMode, host: string) => void;
@@ -62,7 +64,7 @@ export function useCreationWorkspace({
6264
const [isSending, setIsSending] = useState(false);
6365

6466
// Centralized draft workspace settings with automatic persistence
65-
const { settings, setRuntimeOptions, setTrunkBranch, getRuntimeString } =
67+
const { settings, setRuntimeOptions, setTrunkBranch, setFetchLatest, getRuntimeString } =
6668
useDraftWorkspaceSettings(projectPath, branches, recommendedTrunk);
6769

6870
// Get send options from shared hook (uses project-scoped storage key)
@@ -107,6 +109,7 @@ export function useCreationWorkspace({
107109
runtimeConfig,
108110
projectPath, // Pass projectPath when workspaceId is null
109111
trunkBranch: settings.trunkBranch, // Pass selected trunk branch from settings
112+
fetchLatest: settings.fetchLatest, // Fetch remote updates before branching when requested
110113
});
111114

112115
if (!result.success) {
@@ -139,13 +142,16 @@ export function useCreationWorkspace({
139142
getRuntimeString,
140143
sendMessageOptions,
141144
settings.trunkBranch,
145+
settings.fetchLatest,
142146
]
143147
);
144148

145149
return {
146150
branches,
147151
trunkBranch: settings.trunkBranch,
148152
setTrunkBranch,
153+
fetchLatest: settings.fetchLatest,
154+
setFetchLatest,
149155
runtimeMode: settings.runtimeMode,
150156
sshHost: settings.sshHost,
151157
setRuntimeOptions,

src/browser/contexts/WorkspaceContext.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
222222
projectPath: string,
223223
branchName: string,
224224
trunkBranch: string,
225-
runtimeConfig?: RuntimeConfig
225+
runtimeConfig?: RuntimeConfig,
226+
options?: { fetchLatest?: boolean }
226227
) => {
227228
console.assert(
228229
typeof trunkBranch === "string" && trunkBranch.trim().length > 0,
@@ -232,7 +233,8 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
232233
projectPath,
233234
branchName,
234235
trunkBranch,
235-
runtimeConfig
236+
runtimeConfig,
237+
options
236238
);
237239
if (result.success) {
238240
// Backend has already updated the config - reload projects to get updated state

src/browser/hooks/useDraftWorkspaceSettings.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getModelKey,
1414
getRuntimeKey,
1515
getTrunkBranchKey,
16+
getFetchLatestKey,
1617
getProjectScopeId,
1718
} from "@/common/constants/storage";
1819
import type { UIMode } from "@/common/types/mode";
@@ -33,6 +34,7 @@ export interface DraftWorkspaceSettings {
3334
runtimeMode: RuntimeMode;
3435
sshHost: string;
3536
trunkBranch: string;
37+
fetchLatest: boolean;
3638
}
3739

3840
/**
@@ -52,6 +54,7 @@ export function useDraftWorkspaceSettings(
5254
settings: DraftWorkspaceSettings;
5355
setRuntimeOptions: (mode: RuntimeMode, host: string) => void;
5456
setTrunkBranch: (branch: string) => void;
57+
setFetchLatest: (value: boolean) => void;
5558
getRuntimeString: () => string | undefined;
5659
} {
5760
// Global AI settings (read-only from global state)
@@ -74,7 +77,13 @@ export function useDraftWorkspaceSettings(
7477
{ listener: true }
7578
);
7679

77-
// Project-scoped trunk branch preference (persisted per project)
80+
// Project-scoped workspace creation preferences (persisted per project)
81+
const [fetchLatest, setFetchLatest] = usePersistedState<boolean>(
82+
getFetchLatestKey(projectPath),
83+
false,
84+
{ listener: true }
85+
);
86+
7887
const [trunkBranch, setTrunkBranch] = usePersistedState<string>(
7988
getTrunkBranchKey(projectPath),
8089
"",
@@ -112,9 +121,11 @@ export function useDraftWorkspaceSettings(
112121
runtimeMode,
113122
sshHost,
114123
trunkBranch,
124+
fetchLatest,
115125
},
116126
setRuntimeOptions,
117127
setTrunkBranch,
128+
setFetchLatest,
118129
getRuntimeString,
119130
};
120131
}

src/browser/utils/chatCommands.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export interface CreateWorkspaceOptions {
7171
runtime?: string;
7272
startMessage?: string;
7373
sendMessageOptions?: SendMessageOptions;
74+
fetchLatest?: boolean;
7475
}
7576

7677
export interface CreateWorkspaceResult {
@@ -107,12 +108,15 @@ export async function createNewWorkspace(
107108

108109
// Parse runtime config if provided
109110
const runtimeConfig = parseRuntimeString(effectiveRuntime, options.workspaceName);
111+
const creationOptions =
112+
options.fetchLatest !== undefined ? { fetchLatest: options.fetchLatest } : undefined;
110113

111114
const result = await window.api.workspace.create(
112115
options.projectPath,
113116
options.workspaceName,
114117
effectiveTrunk,
115-
runtimeConfig
118+
runtimeConfig,
119+
creationOptions
116120
);
117121

118122
if (!result.success) {

src/common/constants/storage.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ export function getTrunkBranchKey(projectPath: string): string {
108108
return `trunkBranch:${projectPath}`;
109109
}
110110

111+
/**
112+
* Get the localStorage key for the fetch-latest preference for a project
113+
* Stores the last fetch option state when creating a workspace
114+
* Format: "fetchLatest:{projectPath}"
115+
*/
116+
export function getFetchLatestKey(projectPath: string): string {
117+
return `fetchLatest:${projectPath}`;
118+
}
119+
111120
/**
112121
* Get the localStorage key for the 1M context preference (global)
113122
* Format: "use1MContext"

src/common/types/ipc.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export interface SendMessageOptions {
188188
maxOutputTokens?: number;
189189
providerOptions?: MuxProviderOptions;
190190
mode?: string; // Mode name - frontend narrows to specific values, backend accepts any string
191+
fetchLatest?: boolean;
191192
muxMetadata?: MuxFrontendMetadata; // Frontend-defined metadata, backend treats as black-box
192193
}
193194

@@ -233,7 +234,8 @@ export interface IPCApi {
233234
projectPath: string,
234235
branchName: string,
235236
trunkBranch: string,
236-
runtimeConfig?: RuntimeConfig
237+
runtimeConfig?: RuntimeConfig,
238+
options?: { fetchLatest?: boolean }
237239
): Promise<
238240
{ success: true; metadata: FrontendWorkspaceMetadata } | { success: false; error: string }
239241
>;
@@ -260,6 +262,7 @@ export interface IPCApi {
260262
runtimeConfig?: RuntimeConfig;
261263
projectPath?: string; // Required when workspaceId is null
262264
trunkBranch?: string; // Optional - trunk branch to branch from (when workspaceId is null)
265+
fetchLatest?: boolean; // Optional - fetch origin before branching (when workspaceId is null)
263266
}
264267
): Promise<
265268
| Result<void, SendMessageError>

src/desktop/preload.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,14 @@ const api: IPCApi = {
5454
},
5555
workspace: {
5656
list: () => ipcRenderer.invoke(IPC_CHANNELS.WORKSPACE_LIST),
57-
create: (projectPath, branchName, trunkBranch: string, runtimeConfig?) =>
57+
create: (projectPath, branchName, trunkBranch: string, runtimeConfig?, options?) =>
5858
ipcRenderer.invoke(
5959
IPC_CHANNELS.WORKSPACE_CREATE,
6060
projectPath,
6161
branchName,
6262
trunkBranch,
63-
runtimeConfig
63+
runtimeConfig,
64+
options
6465
),
6566
remove: (workspaceId: string, options?: { force?: boolean }) =>
6667
ipcRenderer.invoke(IPC_CHANNELS.WORKSPACE_REMOVE, workspaceId, options),

0 commit comments

Comments
 (0)