diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index dfd79904..13a9ac65 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -2355,6 +2355,12 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo console.error('Message send failed:', rawMessage || error); + const currentInput = textareaRef.current?.value ?? messageRef.current; + if (newSessionDraftOpen && inputSnapshot.message && (!currentInput || currentInput === inputSnapshot.message)) { + setMessage(inputSnapshot.message); + saveStoredDraft(null, inputSnapshot.message); + } + const isSoftNetworkError = normalized.includes('timeout') || normalized.includes('timed out') || diff --git a/packages/ui/src/sync/session-ui-store.test.js b/packages/ui/src/sync/session-ui-store.test.js index 276385a0..725cc36e 100644 --- a/packages/ui/src/sync/session-ui-store.test.js +++ b/packages/ui/src/sync/session-ui-store.test.js @@ -283,6 +283,35 @@ describe('openNewSessionDraft project binding', () => { }); }); +describe('createSession draft lifecycle', () => { + let originalCreateSession; + + beforeEach(() => { + originalCreateSession = opencodeClient.createSession; + useSessionUIStore.setState({ + currentSessionId: null, + currentSessionDirectory: null, + newSessionDraft: { open: true, directoryOverride: '/projects/alpha', parentID: null, title: 'Draft title' }, + }); + }); + + afterEach(() => { + opencodeClient.createSession = originalCreateSession; + }); + + test('keeps the draft open when session creation fails', async () => { + opencodeClient.createSession = async () => { + throw new Error('offline'); + }; + + const session = await useSessionUIStore.getState().createSession('Draft title', '/projects/alpha'); + + expect(session).toBeNull(); + expect(useSessionUIStore.getState().newSessionDraft.open).toBe(true); + expect(useSessionUIStore.getState().newSessionDraft.title).toBe('Draft title'); + }); +}); + describe('routeMessage skill invocation', () => { // OpenCode registers every skill as a command (source: "skill"), so a skill // selected from the slash menu must be dispatched via session.command so its diff --git a/packages/ui/src/sync/session-ui-store.ts b/packages/ui/src/sync/session-ui-store.ts index 1a2ad75e..e4f92def 100644 --- a/packages/ui/src/sync/session-ui-store.ts +++ b/packages/ui/src/sync/session-ui-store.ts @@ -1196,13 +1196,14 @@ export const useSessionUIStore = create()((set, get) => ({ createSession: async (title, directoryOverride, parentID, metadata) => { const draft = get().newSessionDraft const targetFolderId = draft.targetFolderId - get().closeNewSessionDraft() try { const dir = directoryOverride ?? opencodeClient.getDirectory() const session = await createSessionAction(title, dir, parentID ?? null, metadata) if (!session) return null + get().closeNewSessionDraft() + if (targetFolderId) { const scopeKey = directoryOverride || get().lastLoadedDirectory || session.directory if (scopeKey) {