diff --git a/web/app/src/pages/WorkspacePage/components/ProfilePreviewPopover/ProfilePreviewPopover.tsx b/web/app/src/pages/WorkspacePage/components/ProfilePreviewPopover/ProfilePreviewPopover.tsx index efea21dd..1e8ef624 100644 --- a/web/app/src/pages/WorkspacePage/components/ProfilePreviewPopover/ProfilePreviewPopover.tsx +++ b/web/app/src/pages/WorkspacePage/components/ProfilePreviewPopover/ProfilePreviewPopover.tsx @@ -5,6 +5,7 @@ import { ProfilePreviewContent, type ProfilePreviewAnchorRect } from "@/componen import { IconButton } from "@/components/ui"; import type { AgentLike } from "@/models/agents"; import type { IMUser, TranslateFn } from "@/models/conversations"; +import { rootZoomScale } from "@/shared/lib/appScale"; export type ProfilePreviewPopoverProps = { agent: AgentLike | null; @@ -23,11 +24,6 @@ function clamp(value: number, min: number, max: number): number { return Math.min(max, Math.max(min, value)); } -function rootZoomScale(): number { - const zoom = Number.parseFloat(window.getComputedStyle(document.documentElement).zoom); - return Number.isFinite(zoom) && zoom > 0 ? zoom : 1; -} - function profilePreviewStyle(anchorRect: ProfilePreviewAnchorRect | null | undefined, cardHeight?: number | null) { const scale = rootZoomScale(); const offset = 12; diff --git a/web/app/src/pages/WorkspacePage/components/WorkspaceSidebar/WorkspacePrimaryNavigation.tsx b/web/app/src/pages/WorkspacePage/components/WorkspaceSidebar/WorkspacePrimaryNavigation.tsx index d89fa9ca..a990e4b4 100644 --- a/web/app/src/pages/WorkspacePage/components/WorkspaceSidebar/WorkspacePrimaryNavigation.tsx +++ b/web/app/src/pages/WorkspacePage/components/WorkspaceSidebar/WorkspacePrimaryNavigation.tsx @@ -1,4 +1,5 @@ import { classNames } from "@/shared/lib/classNames"; +import { rootZoomScale } from "@/shared/lib/appScale"; import styles from "./WorkspaceSidebar.module.css"; import { useCallback, useId, useRef, useState } from "react"; import type { CSSProperties, ReactNode } from "react"; @@ -71,9 +72,10 @@ function PrimaryNavigationButton({ return; } const rect = buttonRef.current.getBoundingClientRect(); + const scale = rootZoomScale(); setTooltipStyle({ - left: rect.right + 10, - top: rect.top + rect.height / 2, + left: (rect.right + 10) / scale, + top: (rect.top + rect.height / 2) / scale, }); }, [collapsed]); diff --git a/web/app/src/shared/lib/appScale.ts b/web/app/src/shared/lib/appScale.ts new file mode 100644 index 00000000..5b994cab --- /dev/null +++ b/web/app/src/shared/lib/appScale.ts @@ -0,0 +1,4 @@ +export function rootZoomScale(): number { + const zoom = Number.parseFloat(window.getComputedStyle(document.documentElement).zoom); + return Number.isFinite(zoom) && zoom > 0 ? zoom : 1; +} diff --git a/web/app/tests/components/WorkspaceSidebar.test.tsx b/web/app/tests/components/WorkspaceSidebar.test.tsx index d40c54cf..9c4834aa 100644 --- a/web/app/tests/components/WorkspaceSidebar.test.tsx +++ b/web/app/tests/components/WorkspaceSidebar.test.tsx @@ -314,6 +314,42 @@ describe("WorkspaceSidebar", () => { expect(contextAside).not.toHaveAttribute("inert"); }); + it("aligns collapsed navigation tooltips with their buttons when the page is zoomed", () => { + const previousZoom = document.documentElement.style.zoom; + document.documentElement.style.zoom = "0.8"; + const view = renderSidebar({ isSidebarCollapsed: true }); + const button = screen.getByRole("button", { name: "Model Providers" }); + const buttonRect = { + bottom: 328, + height: 48, + left: 16, + right: 64, + top: 280, + width: 48, + x: 16, + y: 280, + toJSON: () => ({}), + }; + const rectSpy = vi.spyOn(button, "getBoundingClientRect").mockReturnValue(buttonRect); + + try { + fireEvent.mouseEnter(button); + + const tooltip = screen.getByRole("tooltip"); + const scale = 0.8; + const visualLeft = Number.parseFloat(tooltip.style.left) * scale; + const visualTop = Number.parseFloat(tooltip.style.top) * scale; + + expect(visualLeft).toBe(buttonRect.right + 10); + expect(visualTop).toBe(buttonRect.top + buttonRect.height / 2); + expect(button).toHaveAttribute("aria-describedby", tooltip.id); + } finally { + rectSpy.mockRestore(); + view.unmount(); + document.documentElement.style.zoom = previousZoom; + } + }); + it("opens the scheduled task view from the task navigation", () => { const onSelectTask = vi.fn(); const onSelectTaskBoardView = vi.fn();