diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx index 3763b5d5e2a..c3f703a3abb 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx @@ -29,6 +29,7 @@ import { useAgentLaunchPreferences } from "renderer/hooks/useAgentLaunchPreferen import { useEnabledAgents } from "renderer/hooks/useEnabledAgents"; import { PLATFORM } from "renderer/hotkeys"; import { useNewWorkspaceModalOpen } from "renderer/stores/new-workspace-modal"; +import { useV2WorkspaceCreateDefaultsStore } from "renderer/stores/v2-workspace-create-defaults"; import { useDashboardNewWorkspaceDraft } from "../../../DashboardNewWorkspaceDraftContext"; import { DevicePicker } from "../components/DevicePicker"; import { AttachmentButtons } from "./components/AttachmentButtons"; @@ -70,6 +71,19 @@ export function PromptGroup({ const navigate = useNavigate(); const attachments = useProviderAttachments(); const needsSetup = selectedProject?.needsSetup === true; + const persistedBaseBranchDefault = useV2WorkspaceCreateDefaultsStore( + (state) => + projectId ? (state.baseBranchesByProjectId[projectId] ?? null) : null, + ); + const setBaseBranchDefault = useV2WorkspaceCreateDefaultsStore( + (state) => state.setBaseBranchDefault, + ); + const clearBaseBranchDefault = useV2WorkspaceCreateDefaultsStore( + (state) => state.clearBaseBranchDefault, + ); + const setLastHostTarget = useV2WorkspaceCreateDefaultsStore( + (state) => state.setLastHostTarget, + ); const handleGoToSetup = useCallback(() => { if (!selectedProject?.id) return; const targetProjectId = selectedProject.id; @@ -111,7 +125,8 @@ export function PromptGroup({ ? sanitizeUserBranchName(branchName) : friendlyFallback; - // Reset baseBranch on project or host change. + // Reset baseBranch on project or host change, defaulting to the user's + // last selected branch for that project when one exists. const previousProjectIdRef = useRef(projectId); const previousHostRef = useRef(JSON.stringify(hostTarget)); useEffect(() => { @@ -122,9 +137,12 @@ export function PromptGroup({ ) { previousProjectIdRef.current = projectId; previousHostRef.current = nextHost; - updateDraft({ baseBranch: null, baseBranchSource: null }); + updateDraft({ + baseBranch: persistedBaseBranchDefault?.branchName ?? null, + baseBranchSource: persistedBaseBranchDefault?.source ?? null, + }); } - }, [projectId, hostTarget, updateDraft]); + }, [projectId, hostTarget, persistedBaseBranchDefault, updateDraft]); // ── Branch picker controller ───────────────────────────────────── const { pickerProps } = useBranchPickerController({ @@ -133,8 +151,16 @@ export function PromptGroup({ baseBranch, runSetupScript: draft.runSetupScript, typedWorkspaceName: workspaceName, - onBaseBranchChange: (branch, source) => - updateDraft({ baseBranch: branch, baseBranchSource: source }), + onBaseBranchChange: (branch, source) => { + if (projectId) { + if (branch && source) { + setBaseBranchDefault(projectId, branch, source); + } else { + clearBaseBranchDefault(projectId); + } + } + updateDraft({ baseBranch: branch, baseBranchSource: source }); + }, closeModal, }); @@ -389,7 +415,10 @@ export function PromptGroup({
updateDraft({ hostTarget: t })} + onSelectHostTarget={(t) => { + setLastHostTarget(t); + updateDraft({ hostTarget: t }); + }} /> state.setLastProjectId, + ); const collections = useCollections(); const { data: session } = authClient.useSession(); const activeOrganizationId = env.SKIP_ENV_VALIDATION @@ -76,12 +80,29 @@ export function DashboardNewWorkspaceModalContent({ const areProjectsReady = v2Projects !== undefined; const appliedPreSelectionRef = useRef(null); + const appliedHostTargetRef = useRef(false); useEffect(() => { if (!isOpen) { appliedPreSelectionRef.current = null; + appliedHostTargetRef.current = false; + return; + } + if (appliedHostTargetRef.current) return; + appliedHostTargetRef.current = true; + const persistedHostTarget = + useV2WorkspaceCreateDefaultsStore.getState().lastHostTarget; + const validHostTarget = + persistedHostTarget?.kind === "local" + ? persistedHostTarget + : persistedHostTarget?.kind === "host" && + typeof persistedHostTarget.hostId === "string" + ? persistedHostTarget + : null; + if (validHostTarget) { + updateDraft({ hostTarget: validHostTarget }); } - }, [isOpen]); + }, [isOpen, updateDraft]); useEffect(() => { if (!isOpen) return; @@ -109,7 +130,15 @@ export function DashboardNewWorkspaceModalContent({ (project) => project.id === draft.selectedProjectId, ); if (!hasSelectedProject) { - updateDraft({ selectedProjectId: recentProjects[0]?.id ?? null }); + const { lastProjectId } = useV2WorkspaceCreateDefaultsStore.getState(); + const persistedProjectId = + lastProjectId && + recentProjects.some((project) => project.id === lastProjectId) + ? lastProjectId + : null; + updateDraft({ + selectedProjectId: persistedProjectId ?? recentProjects[0]?.id ?? null, + }); } }, [ draft.selectedProjectId, @@ -130,9 +159,10 @@ export function DashboardNewWorkspaceModalContent({ projectId={draft.selectedProjectId} selectedProject={selectedProject} recentProjects={recentProjects.filter((project) => Boolean(project.id))} - onSelectProject={(selectedProjectId) => - updateDraft({ selectedProjectId }) - } + onSelectProject={(selectedProjectId) => { + setLastProjectId(selectedProjectId); + updateDraft({ selectedProjectId }); + }} />
); diff --git a/apps/desktop/src/renderer/stores/v2-workspace-create-defaults.ts b/apps/desktop/src/renderer/stores/v2-workspace-create-defaults.ts new file mode 100644 index 00000000000..25f40660eb3 --- /dev/null +++ b/apps/desktop/src/renderer/stores/v2-workspace-create-defaults.ts @@ -0,0 +1,69 @@ +import { create } from "zustand"; +import { devtools, persist } from "zustand/middleware"; + +export type V2WorkspaceCreateBaseBranchSource = "local" | "remote-tracking"; + +export interface V2WorkspaceCreateBaseBranchDefault { + branchName: string; + source: V2WorkspaceCreateBaseBranchSource; +} + +export type V2WorkspaceCreateHostTarget = + | { kind: "local" } + | { kind: "host"; hostId: string }; + +interface V2WorkspaceCreateDefaultsState { + lastProjectId: string | null; + baseBranchesByProjectId: Record; + lastHostTarget: V2WorkspaceCreateHostTarget | null; + + setLastProjectId: (projectId: string | null) => void; + setBaseBranchDefault: ( + projectId: string, + branchName: string, + source: V2WorkspaceCreateBaseBranchSource, + ) => void; + clearBaseBranchDefault: (projectId: string) => void; + setLastHostTarget: (target: V2WorkspaceCreateHostTarget) => void; +} + +export const useV2WorkspaceCreateDefaultsStore = + create()( + devtools( + persist( + (set) => ({ + lastProjectId: null, + baseBranchesByProjectId: {}, + lastHostTarget: null, + + setLastProjectId: (projectId) => set({ lastProjectId: projectId }), + + setBaseBranchDefault: (projectId, branchName, source) => { + const trimmed = branchName.trim(); + if (!trimmed) return; + set((state) => ({ + baseBranchesByProjectId: { + ...state.baseBranchesByProjectId, + [projectId]: { branchName: trimmed, source }, + }, + })); + }, + + clearBaseBranchDefault: (projectId) => + set((state) => { + if (!(projectId in state.baseBranchesByProjectId)) return state; + const next = { ...state.baseBranchesByProjectId }; + delete next[projectId]; + return { baseBranchesByProjectId: next }; + }), + + setLastHostTarget: (target) => set({ lastHostTarget: target }), + }), + { + name: "v2-workspace-create-defaults", + version: 1, + }, + ), + { name: "V2WorkspaceCreateDefaultsStore" }, + ), + );