Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions apps/web/src/components/ChatView.browser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,32 @@ it("a rejected send leaves the timeline when a new draft was typed meanwhile", a
await expect.element(page.getByText("Failed prompt")).not.toBeInTheDocument();
expect(editor.element().textContent).toContain("Next draft");
});

it("Enter on an empty prompt resumes an interrupted thread", async () => {
const history = threadWithHistory("interrupted", 1);
const interruptedRun = { ...history.runs[0]!, status: "interrupted" as const };
const thread = { ...history, runs: [interruptedRun] };
using _environment = installFakeEnvironment([thread], { acceptCommands: true });
__setClientSettingsForTests({
...DEFAULT_CLIENT_SETTINGS,
onboardingCompletedAt: "2026-01-01T00:00:00.000Z",
});
const router = getRouter(
createMemoryHistory({ initialEntries: [`/${ENVIRONMENT_ID}/${thread.id}`] }),
);
await router.load();
await using _app = await render(<AppRoot router={router} />, {
// AppRoot brings its own router.
router: false,
});
await expect.element(page.getByRole("button", { name: "Resume thread" })).toBeVisible();

await userEvent.click(page.getByTestId("composer-editor"));
await userEvent.keyboard("{Enter}");

await vi.waitFor(() =>
expect(dispatchedCommands()).toContainEqual(
expect.objectContaining({ manualContinuationOfRunId: interruptedRun.id }),
),
);
});
7 changes: 7 additions & 0 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2143,6 +2143,13 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
});
return false;
}
// An empty draft has nothing to send; Enter does what the Resume thread button does.
if (showResumeAction && !activePendingProgress) {
event?.preventDefault();
if (isSendBusy || environmentUnavailable !== null) return false;
onResume();
return true;
}
const submission = submitComposerDraft({
prompt: promptRef.current,
submissionTarget,
Expand Down
Loading