From 909073390860e47663c9f65f29cec12894913e14 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 15 Aug 2026 04:28:07 +0000 Subject: [PATCH] fix(sessions): rewrite stale new-chat drafts before send When a regular new-chat draft inherits a deleted lastDirectory, update the visible draft target to the active project immediately. lastDirectory still stays unchanged until session creation succeeds. Co-authored-by: serkraser --- packages/ui/src/sync/session-ui-store.test.js | 16 ++++++++++ packages/ui/src/sync/session-ui-store.ts | 29 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/packages/ui/src/sync/session-ui-store.test.js b/packages/ui/src/sync/session-ui-store.test.js index 565d5294..7d016857 100644 --- a/packages/ui/src/sync/session-ui-store.test.js +++ b/packages/ui/src/sync/session-ui-store.test.js @@ -456,6 +456,22 @@ describe('createSession draft lifecycle', () => { expect(useSessionUIStore.getState().newSessionDraft.title).toBe('Draft title'); }); + test('rewrites an implicit new-chat draft to the active project before the session is created', async () => { + useProjectsStore.setState({ + projects: [{ id: 'project-main', path: '/projects/main', label: 'Main' }], + activeProjectId: 'project-main', + }); + useDirectoryStore.getState().setDirectory('/private/deleted-worktree', { showOverlay: false }); + opencodeClient.getDirectoryAvailability = async () => 'missing'; + + useSessionUIStore.getState().openNewSessionDraft(); + await Bun.sleep(0); + + expect(useSessionUIStore.getState().newSessionDraft.directoryOverride).toBe('/projects/main'); + expect(useSessionUIStore.getState().newSessionDraft.selectedProjectId).toBe('project-main'); + expect(getDeferredSafeStorage().getItem('lastDirectory')).toBe('/private/deleted-worktree'); + }); + test('falls back to the current active project when a regular new-chat directory is missing', async () => { const createSessionCalls = []; useProjectsStore.setState({ diff --git a/packages/ui/src/sync/session-ui-store.ts b/packages/ui/src/sync/session-ui-store.ts index ac4fa453..7ca29a6a 100644 --- a/packages/ui/src/sync/session-ui-store.ts +++ b/packages/ui/src/sync/session-ui-store.ts @@ -660,6 +660,33 @@ const resolveCreatableDraftDirectory = async ( } } +const recoverStaleDraftDirectory = async (openedDraft: NewSessionDraftState): Promise => { + const resolved = await resolveCreatableDraftDirectory(openedDraft, openedDraft.directoryOverride) + if (resolved.status !== "ok") return + const recovered = normalizePath(resolved.directory ?? null) + const original = normalizePath(openedDraft.directoryOverride) + if (!recovered || recovered === original) return + + const currentDraft = useSessionUIStore.getState().newSessionDraft + if (!currentDraft.open) return + if (currentDraft.preserveDirectoryOverride === true) return + if (currentDraft.pendingWorktreeRequestId) return + if (normalizePath(currentDraft.directoryOverride) !== original) return + + const recoveredProject = useProjectsStore.getState().projects.find((project) => ( + normalizePath(project.path) === recovered + )) + const nextDraft: NewSessionDraftState = { + ...currentDraft, + selectedProjectId: recoveredProject?.id ?? currentDraft.selectedProjectId, + directoryOverride: recovered, + } + useSessionUIStore.setState({ newSessionDraft: nextDraft }) + writeRuntimeSessionMemory(runtimeMemoryKey(), { draft: nextDraft }) + persistDraftTarget({ projectId: nextDraft.selectedProjectId ?? null, directory: recovered }) + void activateConfigForDirectory(recovered) +} + export async function materializeOpenDraftSession(selection: { providerID: string modelID: string @@ -1021,6 +1048,8 @@ export const useSessionUIStore = create()((set, get) => ({ if (directory && directory !== useDirectoryStore.getState().currentDirectory) { useDirectoryStore.getState().setDirectory(directory) } + + void recoverStaleDraftDirectory(nextDraft) }, // ---------------------------------------------------------------------------