From dc6020769dc6bfb2c33dda0b43d934746c82b9b2 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Tue, 28 Apr 2026 13:34:38 -0700 Subject: [PATCH 1/4] feat(desktop): persist last project + base branch in v2 workspace modal Remember the user's most recent project and per-project base branch in localStorage so the new-workspace modal pre-fills them on next open. Uses the same plain localStorage pattern as useAgentLaunchPreferences. --- .../lib/v2-workspace-create-defaults.ts | 60 +++++++++++++++++++ .../PromptGroup/PromptGroup.tsx | 21 +++++-- .../DashboardNewWorkspaceModalContent.tsx | 21 +++++-- 3 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts diff --git a/apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts b/apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts new file mode 100644 index 00000000000..5644e9fd8e9 --- /dev/null +++ b/apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts @@ -0,0 +1,60 @@ +export type V2WorkspaceCreateBaseBranchSource = "local" | "remote-tracking"; + +export interface V2WorkspaceCreateBaseBranchDefault { + branchName: string; + source: V2WorkspaceCreateBaseBranchSource; +} + +const LAST_PROJECT_ID_KEY = "v2-workspace-create:last-project-id"; +const BASE_BRANCHES_KEY = "v2-workspace-create:base-branches"; + +export function getLastProjectId(): string | null { + if (typeof window === "undefined") return null; + return window.localStorage.getItem(LAST_PROJECT_ID_KEY); +} + +export function setLastProjectId(projectId: string | null): void { + if (typeof window === "undefined") return; + if (projectId) { + window.localStorage.setItem(LAST_PROJECT_ID_KEY, projectId); + return; + } + window.localStorage.removeItem(LAST_PROJECT_ID_KEY); +} + +function readBaseBranches(): Record< + string, + V2WorkspaceCreateBaseBranchDefault +> { + if (typeof window === "undefined") return {}; + const raw = window.localStorage.getItem(BASE_BRANCHES_KEY); + if (!raw) return {}; + try { + return JSON.parse(raw) as Record< + string, + V2WorkspaceCreateBaseBranchDefault + >; + } catch { + return {}; + } +} + +export function getBaseBranchDefault( + projectId: string | null, +): V2WorkspaceCreateBaseBranchDefault | null { + if (!projectId) return null; + return readBaseBranches()[projectId] ?? null; +} + +export function setBaseBranchDefault( + projectId: string, + branchName: string, + source: V2WorkspaceCreateBaseBranchSource, +): void { + if (typeof window === "undefined") return; + const trimmed = branchName.trim(); + if (!trimmed) return; + const map = readBaseBranches(); + map[projectId] = { branchName: trimmed, source }; + window.localStorage.setItem(BASE_BRANCHES_KEY, JSON.stringify(map)); +} 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..e57942f933c 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 @@ -28,6 +28,10 @@ import { IssueLinkCommand } from "renderer/components/Chat/ChatInterface/compone import { useAgentLaunchPreferences } from "renderer/hooks/useAgentLaunchPreferences"; import { useEnabledAgents } from "renderer/hooks/useEnabledAgents"; import { PLATFORM } from "renderer/hotkeys"; +import { + getBaseBranchDefault, + setBaseBranchDefault, +} from "renderer/lib/v2-workspace-create-defaults"; import { useNewWorkspaceModalOpen } from "renderer/stores/new-workspace-modal"; import { useDashboardNewWorkspaceDraft } from "../../../DashboardNewWorkspaceDraftContext"; import { DevicePicker } from "../components/DevicePicker"; @@ -111,7 +115,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,7 +127,11 @@ export function PromptGroup({ ) { previousProjectIdRef.current = projectId; previousHostRef.current = nextHost; - updateDraft({ baseBranch: null, baseBranchSource: null }); + const persisted = getBaseBranchDefault(projectId); + updateDraft({ + baseBranch: persisted?.branchName ?? null, + baseBranchSource: persisted?.source ?? null, + }); } }, [projectId, hostTarget, updateDraft]); @@ -133,8 +142,12 @@ export function PromptGroup({ baseBranch, runSetupScript: draft.runSetupScript, typedWorkspaceName: workspaceName, - onBaseBranchChange: (branch, source) => - updateDraft({ baseBranch: branch, baseBranchSource: source }), + onBaseBranchChange: (branch, source) => { + if (projectId && branch && source) { + setBaseBranchDefault(projectId, branch, source); + } + updateDraft({ baseBranch: branch, baseBranchSource: source }); + }, closeModal, }); diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx index 7faf257ae6e..fd265ee3cb4 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx @@ -3,6 +3,10 @@ import { useLiveQuery } from "@tanstack/react-db"; import { useEffect, useMemo, useRef } from "react"; import { env } from "renderer/env.renderer"; import { authClient } from "renderer/lib/auth-client"; +import { + getLastProjectId, + setLastProjectId, +} from "renderer/lib/v2-workspace-create-defaults"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; import { MOCK_ORG_ID } from "shared/constants"; import { useDashboardNewWorkspaceDraft } from "../../DashboardNewWorkspaceDraftContext"; @@ -109,7 +113,15 @@ export function DashboardNewWorkspaceModalContent({ (project) => project.id === draft.selectedProjectId, ); if (!hasSelectedProject) { - updateDraft({ selectedProjectId: recentProjects[0]?.id ?? null }); + const lastProjectId = getLastProjectId(); + const persistedProjectId = + lastProjectId && + recentProjects.some((project) => project.id === lastProjectId) + ? lastProjectId + : null; + updateDraft({ + selectedProjectId: persistedProjectId ?? recentProjects[0]?.id ?? null, + }); } }, [ draft.selectedProjectId, @@ -130,9 +142,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 }); + }} /> ); From 1a1a20ee730161e42c848807363d9f91c1963a04 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Tue, 28 Apr 2026 14:40:42 -0700 Subject: [PATCH 2/4] feat(desktop): also persist last selected device in v2 workspace modal --- .../lib/v2-workspace-create-defaults.ts | 26 +++++++++++++++++++ .../PromptGroup/PromptGroup.tsx | 6 ++++- .../DashboardNewWorkspaceModalContent.tsx | 12 ++++++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts b/apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts index 5644e9fd8e9..8119a8f66c8 100644 --- a/apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts +++ b/apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts @@ -5,8 +5,13 @@ export interface V2WorkspaceCreateBaseBranchDefault { source: V2WorkspaceCreateBaseBranchSource; } +export type V2WorkspaceCreateHostTarget = + | { kind: "local" } + | { kind: "host"; hostId: string }; + const LAST_PROJECT_ID_KEY = "v2-workspace-create:last-project-id"; const BASE_BRANCHES_KEY = "v2-workspace-create:base-branches"; +const LAST_HOST_TARGET_KEY = "v2-workspace-create:last-host-target"; export function getLastProjectId(): string | null { if (typeof window === "undefined") return null; @@ -58,3 +63,24 @@ export function setBaseBranchDefault( map[projectId] = { branchName: trimmed, source }; window.localStorage.setItem(BASE_BRANCHES_KEY, JSON.stringify(map)); } + +export function getLastHostTarget(): V2WorkspaceCreateHostTarget | null { + if (typeof window === "undefined") return null; + const raw = window.localStorage.getItem(LAST_HOST_TARGET_KEY); + if (!raw) return null; + try { + const parsed = JSON.parse(raw) as V2WorkspaceCreateHostTarget; + if (parsed.kind === "local") return { kind: "local" }; + if (parsed.kind === "host" && typeof parsed.hostId === "string") { + return { kind: "host", hostId: parsed.hostId }; + } + return null; + } catch { + return null; + } +} + +export function setLastHostTarget(target: V2WorkspaceCreateHostTarget): void { + if (typeof window === "undefined") return; + window.localStorage.setItem(LAST_HOST_TARGET_KEY, JSON.stringify(target)); +} 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 e57942f933c..5ad98818292 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 @@ -31,6 +31,7 @@ import { PLATFORM } from "renderer/hotkeys"; import { getBaseBranchDefault, setBaseBranchDefault, + setLastHostTarget, } from "renderer/lib/v2-workspace-create-defaults"; import { useNewWorkspaceModalOpen } from "renderer/stores/new-workspace-modal"; import { useDashboardNewWorkspaceDraft } from "../../../DashboardNewWorkspaceDraftContext"; @@ -402,7 +403,10 @@ export function PromptGroup({
updateDraft({ hostTarget: t })} + onSelectHostTarget={(t) => { + setLastHostTarget(t); + updateDraft({ hostTarget: t }); + }} /> (null); + const appliedHostTargetRef = useRef(false); useEffect(() => { if (!isOpen) { appliedPreSelectionRef.current = null; + appliedHostTargetRef.current = false; + return; } - }, [isOpen]); + if (appliedHostTargetRef.current) return; + appliedHostTargetRef.current = true; + const persistedHostTarget = getLastHostTarget(); + if (persistedHostTarget) { + updateDraft({ hostTarget: persistedHostTarget }); + } + }, [isOpen, updateDraft]); useEffect(() => { if (!isOpen) return; From 1dde4302a337787c5efaae73c23bfa10052f78ab Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Tue, 28 Apr 2026 16:57:36 -0700 Subject: [PATCH 3/4] refactor(desktop): use zustand persist store for v2 workspace defaults Switches the persistence layer to a zustand persist store, matching the pattern used by v2-project-local-meta and other stores in renderer/stores. Also clears the per-project base-branch default when the user explicitly clears the picker, so a stale default doesn't re-appear on next open. --- .../lib/v2-workspace-create-defaults.ts | 86 ------------------- .../PromptGroup/PromptGroup.tsx | 34 +++++--- .../DashboardNewWorkspaceModalContent.tsx | 14 +-- .../stores/v2-workspace-create-defaults.ts | 69 +++++++++++++++ 4 files changed, 99 insertions(+), 104 deletions(-) delete mode 100644 apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts create mode 100644 apps/desktop/src/renderer/stores/v2-workspace-create-defaults.ts diff --git a/apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts b/apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts deleted file mode 100644 index 8119a8f66c8..00000000000 --- a/apps/desktop/src/renderer/lib/v2-workspace-create-defaults.ts +++ /dev/null @@ -1,86 +0,0 @@ -export type V2WorkspaceCreateBaseBranchSource = "local" | "remote-tracking"; - -export interface V2WorkspaceCreateBaseBranchDefault { - branchName: string; - source: V2WorkspaceCreateBaseBranchSource; -} - -export type V2WorkspaceCreateHostTarget = - | { kind: "local" } - | { kind: "host"; hostId: string }; - -const LAST_PROJECT_ID_KEY = "v2-workspace-create:last-project-id"; -const BASE_BRANCHES_KEY = "v2-workspace-create:base-branches"; -const LAST_HOST_TARGET_KEY = "v2-workspace-create:last-host-target"; - -export function getLastProjectId(): string | null { - if (typeof window === "undefined") return null; - return window.localStorage.getItem(LAST_PROJECT_ID_KEY); -} - -export function setLastProjectId(projectId: string | null): void { - if (typeof window === "undefined") return; - if (projectId) { - window.localStorage.setItem(LAST_PROJECT_ID_KEY, projectId); - return; - } - window.localStorage.removeItem(LAST_PROJECT_ID_KEY); -} - -function readBaseBranches(): Record< - string, - V2WorkspaceCreateBaseBranchDefault -> { - if (typeof window === "undefined") return {}; - const raw = window.localStorage.getItem(BASE_BRANCHES_KEY); - if (!raw) return {}; - try { - return JSON.parse(raw) as Record< - string, - V2WorkspaceCreateBaseBranchDefault - >; - } catch { - return {}; - } -} - -export function getBaseBranchDefault( - projectId: string | null, -): V2WorkspaceCreateBaseBranchDefault | null { - if (!projectId) return null; - return readBaseBranches()[projectId] ?? null; -} - -export function setBaseBranchDefault( - projectId: string, - branchName: string, - source: V2WorkspaceCreateBaseBranchSource, -): void { - if (typeof window === "undefined") return; - const trimmed = branchName.trim(); - if (!trimmed) return; - const map = readBaseBranches(); - map[projectId] = { branchName: trimmed, source }; - window.localStorage.setItem(BASE_BRANCHES_KEY, JSON.stringify(map)); -} - -export function getLastHostTarget(): V2WorkspaceCreateHostTarget | null { - if (typeof window === "undefined") return null; - const raw = window.localStorage.getItem(LAST_HOST_TARGET_KEY); - if (!raw) return null; - try { - const parsed = JSON.parse(raw) as V2WorkspaceCreateHostTarget; - if (parsed.kind === "local") return { kind: "local" }; - if (parsed.kind === "host" && typeof parsed.hostId === "string") { - return { kind: "host", hostId: parsed.hostId }; - } - return null; - } catch { - return null; - } -} - -export function setLastHostTarget(target: V2WorkspaceCreateHostTarget): void { - if (typeof window === "undefined") return; - window.localStorage.setItem(LAST_HOST_TARGET_KEY, JSON.stringify(target)); -} 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 5ad98818292..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 @@ -28,12 +28,8 @@ import { IssueLinkCommand } from "renderer/components/Chat/ChatInterface/compone import { useAgentLaunchPreferences } from "renderer/hooks/useAgentLaunchPreferences"; import { useEnabledAgents } from "renderer/hooks/useEnabledAgents"; import { PLATFORM } from "renderer/hotkeys"; -import { - getBaseBranchDefault, - setBaseBranchDefault, - setLastHostTarget, -} from "renderer/lib/v2-workspace-create-defaults"; 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"; @@ -75,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; @@ -128,13 +137,12 @@ export function PromptGroup({ ) { previousProjectIdRef.current = projectId; previousHostRef.current = nextHost; - const persisted = getBaseBranchDefault(projectId); updateDraft({ - baseBranch: persisted?.branchName ?? null, - baseBranchSource: persisted?.source ?? null, + baseBranch: persistedBaseBranchDefault?.branchName ?? null, + baseBranchSource: persistedBaseBranchDefault?.source ?? null, }); } - }, [projectId, hostTarget, updateDraft]); + }, [projectId, hostTarget, persistedBaseBranchDefault, updateDraft]); // ── Branch picker controller ───────────────────────────────────── const { pickerProps } = useBranchPickerController({ @@ -144,8 +152,12 @@ export function PromptGroup({ runSetupScript: draft.runSetupScript, typedWorkspaceName: workspaceName, onBaseBranchChange: (branch, source) => { - if (projectId && branch && source) { - setBaseBranchDefault(projectId, branch, source); + if (projectId) { + if (branch && source) { + setBaseBranchDefault(projectId, branch, source); + } else { + clearBaseBranchDefault(projectId); + } } updateDraft({ baseBranch: branch, baseBranchSource: source }); }, diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx index 1b5eeb5cd57..a8a19c11bf3 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx @@ -3,12 +3,8 @@ import { useLiveQuery } from "@tanstack/react-db"; import { useEffect, useMemo, useRef } from "react"; import { env } from "renderer/env.renderer"; import { authClient } from "renderer/lib/auth-client"; -import { - getLastHostTarget, - getLastProjectId, - setLastProjectId, -} from "renderer/lib/v2-workspace-create-defaults"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; +import { useV2WorkspaceCreateDefaultsStore } from "renderer/stores/v2-workspace-create-defaults"; import { MOCK_ORG_ID } from "shared/constants"; import { useDashboardNewWorkspaceDraft } from "../../DashboardNewWorkspaceDraftContext"; import { PromptGroup } from "../DashboardNewWorkspaceForm/PromptGroup"; @@ -31,6 +27,9 @@ export function DashboardNewWorkspaceModalContent({ preSelectedProjectId, }: DashboardNewWorkspaceModalContentProps) { const { draft, updateDraft } = useDashboardNewWorkspaceDraft(); + const setLastProjectId = useV2WorkspaceCreateDefaultsStore( + (state) => state.setLastProjectId, + ); const collections = useCollections(); const { data: session } = authClient.useSession(); const activeOrganizationId = env.SKIP_ENV_VALIDATION @@ -91,7 +90,8 @@ export function DashboardNewWorkspaceModalContent({ } if (appliedHostTargetRef.current) return; appliedHostTargetRef.current = true; - const persistedHostTarget = getLastHostTarget(); + const persistedHostTarget = + useV2WorkspaceCreateDefaultsStore.getState().lastHostTarget; if (persistedHostTarget) { updateDraft({ hostTarget: persistedHostTarget }); } @@ -123,7 +123,7 @@ export function DashboardNewWorkspaceModalContent({ (project) => project.id === draft.selectedProjectId, ); if (!hasSelectedProject) { - const lastProjectId = getLastProjectId(); + const { lastProjectId } = useV2WorkspaceCreateDefaultsStore.getState(); const persistedProjectId = lastProjectId && recentProjects.some((project) => project.id === lastProjectId) 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" }, + ), + ); From 82ba7bb803d1222a657011e470a8d23f9749313e Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Tue, 28 Apr 2026 17:10:16 -0700 Subject: [PATCH 4/4] fix(desktop): shape-validate persisted lastHostTarget before applying --- .../DashboardNewWorkspaceModalContent.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx index a8a19c11bf3..aed38823323 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/DashboardNewWorkspaceModalContent.tsx @@ -92,8 +92,15 @@ export function DashboardNewWorkspaceModalContent({ appliedHostTargetRef.current = true; const persistedHostTarget = useV2WorkspaceCreateDefaultsStore.getState().lastHostTarget; - if (persistedHostTarget) { - updateDraft({ hostTarget: persistedHostTarget }); + const validHostTarget = + persistedHostTarget?.kind === "local" + ? persistedHostTarget + : persistedHostTarget?.kind === "host" && + typeof persistedHostTarget.hostId === "string" + ? persistedHostTarget + : null; + if (validHostTarget) { + updateDraft({ hostTarget: validHostTarget }); } }, [isOpen, updateDraft]);