fix: close search when starting new chats

maintainer edit: Close the session search dialog when the global new-chat shortcut navigates to the blank chat route, and expose the new shortcut through the sidebar button title so the shortcut is discoverable.
This commit is contained in:
chengyongru 2026-06-04 11:03:06 +08:00 committed by Xubin Ren
parent 4275678b43
commit 54d8d3010b
3 changed files with 50 additions and 3 deletions

View File

@ -813,6 +813,7 @@ function Shell({
navigate(defaultShellRoute()); navigate(defaultShellRoute());
setDraftWorkspaceScope(null); setDraftWorkspaceScope(null);
setWorkspaceError(null); setWorkspaceError(null);
setSessionSearchOpen(false);
setMobileSidebarOpen(false); setMobileSidebarOpen(false);
}, [navigate]); }, [navigate]);

View File

@ -124,6 +124,7 @@ export function Sidebar(props: SidebarProps) {
label={t("sidebar.newChat")} label={t("sidebar.newChat")}
onClick={props.onNewChat} onClick={props.onNewChat}
icon={<SquarePen className="h-4 w-4" />} icon={<SquarePen className="h-4 w-4" />}
shortcut="Cmd/Ctrl+Shift+O"
/> />
<SidebarActionButton <SidebarActionButton
collapsed={collapsed} collapsed={collapsed}
@ -213,6 +214,7 @@ function SidebarActionButton({
onClick, onClick,
active = false, active = false,
className, className,
shortcut,
}: { }: {
collapsed: boolean; collapsed: boolean;
label: string; label: string;
@ -220,14 +222,17 @@ function SidebarActionButton({
onClick: () => void; onClick: () => void;
active?: boolean; active?: boolean;
className?: string; className?: string;
shortcut?: string;
}) { }) {
const title = shortcut ? `${label} (${shortcut})` : collapsed ? label : undefined;
return ( return (
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
aria-label={label} aria-label={label}
aria-current={active ? "page" : undefined} aria-current={active ? "page" : undefined}
title={collapsed ? label : undefined} title={title}
onClick={() => onClick()} onClick={() => onClick()}
className={cn( className={cn(
"group h-8 min-w-0 gap-2 overflow-hidden rounded-full font-medium text-sidebar-foreground/85 hover:bg-sidebar-accent/75 hover:text-sidebar-foreground", "group h-8 min-w-0 gap-2 overflow-hidden rounded-full font-medium text-sidebar-foreground/85 hover:bg-sidebar-accent/75 hover:text-sidebar-foreground",

View File

@ -1354,7 +1354,10 @@ describe("App layout", () => {
expect(createChatSpy).not.toHaveBeenCalled(); expect(createChatSpy).not.toHaveBeenCalled();
}); });
it("starts a new chat from the keyboard shortcut", async () => { it.each([
["Command", { metaKey: true }],
["Control", { ctrlKey: true }],
])("starts a new chat from the %s keyboard shortcut", async (_label, modifier) => {
mockSessions = [ mockSessions = [
{ {
key: "websocket:chat-a", key: "websocket:chat-a",
@ -1369,11 +1372,49 @@ describe("App layout", () => {
render(<App />); render(<App />);
await waitFor(() => expect(connectSpy).toHaveBeenCalled()); await waitFor(() => expect(connectSpy).toHaveBeenCalled());
fireEvent.keyDown(window, { key: "O", shiftKey: true, metaKey: true }); fireEvent.keyDown(window, { key: "O", shiftKey: true, ...modifier });
expect(window.location.hash).toBe("#/new"); expect(window.location.hash).toBe("#/new");
}); });
it("closes search when starting a new chat from the keyboard shortcut", async () => {
mockSessions = [
{
key: "websocket:chat-a",
channel: "websocket",
chatId: "chat-a",
createdAt: "2026-04-16T10:00:00Z",
updatedAt: "2026-04-16T10:00:00Z",
preview: "Existing chat",
},
];
render(<App />);
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
fireEvent.keyDown(window, { key: "k", metaKey: true });
expect(await screen.findByRole("dialog", { name: "Search" })).toBeInTheDocument();
fireEvent.keyDown(window, { key: "O", shiftKey: true, metaKey: true });
await waitFor(() =>
expect(screen.queryByRole("dialog", { name: "Search" })).not.toBeInTheDocument(),
);
expect(window.location.hash).toBe("#/new");
});
it("exposes the new chat keyboard shortcut in the sidebar title", async () => {
render(<App />);
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
expect(within(sidebar).getByRole("button", { name: "New chat" })).toHaveAttribute(
"title",
"New chat (Cmd/Ctrl+Shift+O)",
);
});
it("keeps large sidebars light while search still covers every chat", async () => { it("keeps large sidebars light while search still covers every chat", async () => {
mockSessions = Array.from({ length: 170 }, (_, index) => { mockSessions = Array.from({ length: 170 }, (_, index) => {
const chatId = `chat-${index}`; const chatId = `chat-${index}`;