fix(frontend): make recent chat rows fully clickable (#3733)

* fix(frontend): make recent chat rows fully clickable

* Address recent chat row review comments
This commit is contained in:
zgenu 2026-06-24 09:48:59 +08:00 committed by GitHub
parent cc1df2d038
commit 11415875c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 105 additions and 80 deletions

View File

@ -241,95 +241,91 @@ export function RecentChatList() {
className="group/side-menu-item" className="group/side-menu-item"
> >
<SidebarMenuButton isActive={isActive} asChild> <SidebarMenuButton isActive={isActive} asChild>
<div> <Link
<Link className="text-muted-foreground min-w-0 whitespace-nowrap group-hover/side-menu-item:overflow-hidden"
className="text-muted-foreground flex min-w-0 items-center gap-1.5 pr-7 whitespace-nowrap group-hover/side-menu-item:overflow-hidden" href={pathOfThread(thread)}
href={pathOfThread(thread)} >
> <ThreadChannelIcon source={channelSource} />
<ThreadChannelIcon source={channelSource} /> <span className="min-w-0 truncate">
<span className="min-w-0 truncate"> {titleOfThread(thread)}
{titleOfThread(thread)} </span>
</span> {channelSource && (
{channelSource && ( <span
<span className="bg-muted text-muted-foreground ml-auto inline-flex h-5 max-w-14 shrink-0 items-center rounded-md px-1.5 text-[10px] font-medium"
className="bg-muted text-muted-foreground ml-auto inline-flex h-5 max-w-14 shrink-0 items-center rounded-md px-1.5 text-[10px] font-medium" title={`${channelSource.label} channel`}
title={`${channelSource.label} channel`} >
> <span className="truncate">
<span className="truncate"> {channelSource.label}
{channelSource.label}
</span>
</span> </span>
)} </span>
</Link> )}
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true" && ( </Link>
<DropdownMenu> </SidebarMenuButton>
<DropdownMenuTrigger asChild> {env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true" && (
<SidebarMenuAction <DropdownMenu>
showOnHover <DropdownMenuTrigger asChild>
className="bg-background/50 hover:bg-background" <SidebarMenuAction
> showOnHover
<MoreHorizontal /> className="bg-background/50 hover:bg-background after:left-0!"
<span className="sr-only">{t.common.more}</span> >
</SidebarMenuAction> <MoreHorizontal />
</DropdownMenuTrigger> <span className="sr-only">{t.common.more}</span>
<DropdownMenuContent </SidebarMenuAction>
className="w-48 rounded-lg" </DropdownMenuTrigger>
side={"right"} <DropdownMenuContent
align={"start"} className="w-48 rounded-lg"
> side={"right"}
align={"start"}
>
<DropdownMenuItem
onSelect={() =>
handleRenameClick(
thread.thread_id,
titleOfThread(thread),
)
}
>
<Pencil className="text-muted-foreground" />
<span>{t.common.rename}</span>
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => handleShare(thread)}
>
<Share2 className="text-muted-foreground" />
<span>{t.common.share}</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Download className="text-muted-foreground" />
<span>{t.common.export}</span>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem <DropdownMenuItem
onSelect={() => onSelect={() =>
handleRenameClick( handleExport(thread, "markdown")
thread.thread_id,
titleOfThread(thread),
)
} }
> >
<Pencil className="text-muted-foreground" /> <FileText className="text-muted-foreground" />
<span>{t.common.rename}</span> <span>{t.common.exportAsMarkdown}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onSelect={() => handleShare(thread)} onSelect={() => handleExport(thread, "json")}
> >
<Share2 className="text-muted-foreground" /> <FileJson className="text-muted-foreground" />
<span>{t.common.share}</span> <span>{t.common.exportAsJSON}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSub> </DropdownMenuSubContent>
<DropdownMenuSubTrigger> </DropdownMenuSub>
<Download className="text-muted-foreground" /> <DropdownMenuSeparator />
<span>{t.common.export}</span> <DropdownMenuItem
</DropdownMenuSubTrigger> onSelect={() => handleDelete(thread)}
<DropdownMenuSubContent> >
<DropdownMenuItem <Trash2 className="text-muted-foreground" />
onSelect={() => <span>{t.common.delete}</span>
handleExport(thread, "markdown") </DropdownMenuItem>
} </DropdownMenuContent>
> </DropdownMenu>
<FileText className="text-muted-foreground" /> )}
<span>{t.common.exportAsMarkdown}</span>
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() =>
handleExport(thread, "json")
}
>
<FileJson className="text-muted-foreground" />
<span>{t.common.exportAsJSON}</span>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem
onSelect={() => handleDelete(thread)}
>
<Trash2 className="text-muted-foreground" />
<span>{t.common.delete}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
); );
})} })}

View File

@ -53,6 +53,35 @@ test.describe("Thread history", () => {
await expect(page).toHaveURL(new RegExp(MOCK_THREAD_ID)); await expect(page).toHaveURL(new RegExp(MOCK_THREAD_ID));
}); });
test("clicking blank space in a sidebar thread row navigates to it", async ({
page,
}) => {
mockLangGraphAPI(page, { threads: THREADS });
await page.goto("/workspace/chats/new");
const sidebar = page.locator("[data-sidebar='sidebar']");
const firstThreadItem = sidebar
.locator("[data-sidebar='menu-item']")
.filter({ hasText: "First conversation" })
.first();
await expect(firstThreadItem).toBeVisible({ timeout: 15_000 });
const firstThreadLink = firstThreadItem.getByRole("link");
await expect(firstThreadLink).toBeVisible();
const box = await firstThreadLink.boundingBox();
expect(box).not.toBeNull();
if (!box) {
return;
}
await firstThreadLink.click({ position: { x: 4, y: box.height / 2 } });
await page.waitForURL(`**/workspace/chats/${MOCK_THREAD_ID}`);
await expect(page).toHaveURL(new RegExp(MOCK_THREAD_ID));
});
test("existing thread loads historical messages", async ({ page }) => { test("existing thread loads historical messages", async ({ page }) => {
mockLangGraphAPI(page, { threads: THREADS }); mockLangGraphAPI(page, { threads: THREADS });