fix(webui): show platform-specific new chat shortcut

This commit is contained in:
Xubin Ren 2026-06-04 13:54:52 +08:00
parent 54d8d3010b
commit 87bd56468c
2 changed files with 49 additions and 2 deletions

View File

@ -55,12 +55,29 @@ interface SidebarProps {
hostChromeInset?: boolean;
}
type NavigatorWithUserAgentData = Navigator & {
userAgentData?: { platform?: string };
};
function isApplePlatform(): boolean {
if (typeof navigator === "undefined") return false;
const platform = navigator.platform || "";
const userAgentPlatform =
(navigator as NavigatorWithUserAgentData).userAgentData?.platform || "";
return /mac|iphone|ipad|ipod/i.test(`${platform} ${userAgentPlatform}`);
}
function newChatShortcutLabel(): string {
return isApplePlatform() ? "⌘⇧O" : "Ctrl+Shift+O";
}
export function Sidebar(props: SidebarProps) {
const { t } = useTranslation();
const [menuPortalContainer, setMenuPortalContainer] =
useState<HTMLElement | null>(null);
const collapsed = Boolean(props.collapsed);
const toggleLabel = t("thread.header.toggleSidebar");
const newChatShortcut = newChatShortcutLabel();
return (
<nav
@ -124,7 +141,8 @@ export function Sidebar(props: SidebarProps) {
label={t("sidebar.newChat")}
onClick={props.onNewChat}
icon={<SquarePen className="h-4 w-4" />}
shortcut="Cmd/Ctrl+Shift+O"
shortcut={newChatShortcut}
ariaKeyShortcuts="Meta+Shift+O Control+Shift+O"
/>
<SidebarActionButton
collapsed={collapsed}
@ -215,6 +233,7 @@ function SidebarActionButton({
active = false,
className,
shortcut,
ariaKeyShortcuts,
}: {
collapsed: boolean;
label: string;
@ -223,6 +242,7 @@ function SidebarActionButton({
active?: boolean;
className?: string;
shortcut?: string;
ariaKeyShortcuts?: string;
}) {
const title = shortcut ? `${label} (${shortcut})` : collapsed ? label : undefined;
@ -232,6 +252,7 @@ function SidebarActionButton({
variant="ghost"
aria-label={label}
aria-current={active ? "page" : undefined}
aria-keyshortcuts={ariaKeyShortcuts}
title={title}
onClick={() => onClick()}
className={cn(

View File

@ -15,6 +15,13 @@ let mockSessions: ChatSummary[] = [];
const HERO_GREETING_PATTERN =
/What should we work on\?|Where should we start\?|What are we building today\?|What should we tackle together\?/;
function setNavigatorPlatform(platform: string): void {
Object.defineProperty(window.navigator, "platform", {
configurable: true,
value: platform,
});
}
function jsonResponse(body: unknown): Response {
return {
ok: true,
@ -200,6 +207,7 @@ describe("App layout", () => {
attachSpy.mockReset();
runStatusHandlers.clear();
window.history.replaceState(null, "", "/");
setNavigatorPlatform("Linux x86_64");
localStorage.removeItem("nanobot-webui.sidebar.completed-runs.v1");
vi.mocked(fetchBootstrap).mockReset().mockResolvedValue({
token: "tok",
@ -1409,9 +1417,27 @@ describe("App layout", () => {
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
const newChatButton = within(sidebar).getByRole("button", { name: "New chat" });
expect(newChatButton).toHaveAttribute(
"title",
"New chat (Ctrl+Shift+O)",
);
expect(newChatButton).toHaveAttribute(
"aria-keyshortcuts",
"Meta+Shift+O Control+Shift+O",
);
});
it("uses macOS shortcut glyphs in the sidebar title", async () => {
setNavigatorPlatform("MacIntel");
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)",
"New chat (⌘⇧O)",
);
});