fix(chat): preserve prompt when session creation fails
Keep the new-session draft open until the backend confirms that the session was created successfully. This prevents a failed request from closing the draft and discarding the user's creation context. Restore the exact submitted composer text after a new-session send failure and persist it as the draft. Only restore when the composer is still empty or unchanged, so text entered while the request is pending is not overwritten. Add regression coverage that simulates an offline session creation request and verifies that the draft remains open with its title intact.
This commit is contained in:
@@ -2355,6 +2355,12 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ 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') ||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1196,13 +1196,14 @@ export const useSessionUIStore = create<SessionUIState>()((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) {
|
||||
|
||||
Reference in New Issue
Block a user