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
26 changes: 26 additions & 0 deletions web/app/src/components/ui/Icons/SidebarNavigationIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,32 @@ export function SidebarPuzzlePiece02Icon({ size, ...props }: SidebarNavigationIc
);
}

export function SidebarMcpIcon({ size, ...props }: SidebarNavigationIconProps) {
const resolvedSize = iconSize(size, 16);
return (
<svg width={resolvedSize} height={resolvedSize} viewBox="0 0 16 16" fill="none" {...props}>
<path
d="M1.85339 7.54013L7.96123 1.43229C8.80459 0.588975 10.1719 0.588975 11.0152 1.43229C11.8585 2.2756 11.8585 3.64289 11.0152 4.48621L6.40246 9.09892"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M6.46606 9.03509L11.0151 4.48601C11.8585 3.64269 13.2258 3.64269 14.0691 4.48601L14.1009 4.51781C14.9442 5.36113 14.9442 6.72842 14.1009 7.57173L8.57689 13.0957C8.29578 13.3769 8.29578 13.8326 8.57689 14.1137L9.71113 15.248"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.48812 2.95898L4.97086 7.47624C4.12755 8.31953 4.12755 9.68681 4.97086 10.5302C5.81418 11.3734 7.18146 11.3734 8.02478 10.5302L12.542 6.0129"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

export function SidebarBoxIcon({ size, ...props }: SidebarNavigationIconProps) {
const resolvedSize = iconSize(size, 24);
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,27 @@ 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;
const viewportPadding = 16;
const width = Math.min(360, (window.innerWidth - viewportPadding * 2) / scale);
const visualWidth = width * scale;
const maxLeft = Math.max(viewportPadding, window.innerWidth - viewportPadding - visualWidth);
const visualCardHeight = cardHeight ?? 420 * scale;
const visibleHeight = Math.min(visualCardHeight, window.innerHeight - viewportPadding * 2);
const width = Math.min(360, window.innerWidth - viewportPadding * 2);
const maxLeft = Math.max(viewportPadding, window.innerWidth - viewportPadding - width);
const visibleHeight = Math.min(cardHeight ?? 420, window.innerHeight - viewportPadding * 2);
const maxTop = Math.max(viewportPadding, window.innerHeight - viewportPadding - visibleHeight);

if (!anchorRect) {
return {
top: `${viewportPadding / scale}px`,
left: `${viewportPadding / scale}px`,
top: `${viewportPadding}px`,
left: `${viewportPadding}px`,
width: `${width}px`,
};
}

const hasRoomRight = anchorRect.right + offset + visualWidth <= window.innerWidth - viewportPadding;
const preferredLeft = hasRoomRight ? anchorRect.right + offset : anchorRect.left - visualWidth - offset;
const hasRoomRight = anchorRect.right + offset + width <= window.innerWidth - viewportPadding;
const preferredLeft = hasRoomRight ? anchorRect.right + offset : anchorRect.left - width - offset;
const left = clamp(preferredLeft, viewportPadding, maxLeft);
const top = clamp(anchorRect.top - 12, viewportPadding, maxTop);
return { top: `${top / scale}px`, left: `${left / scale}px`, width: `${width}px` };
return { top: `${top}px`, left: `${left}px`, width: `${width}px` };
}

export function ProfilePreviewPopover({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { classNames } from "@/shared/lib/classNames";
import styles from "./WorkspaceSidebar.module.css";
import { useCallback, useId, useRef, useState } from "react";
import { createPortal } from "react-dom";
import type { CSSProperties, ReactNode } from "react";
import type { WorkspaceContextSectionId } from "./types";

Expand Down Expand Up @@ -101,11 +102,14 @@ function PrimaryNavigationButton({
{typeof item.badge === "number" ? (
<span className={styles.primaryNavBadge}>{formatBadge(item.badge)}</span>
) : null}
{collapsed && tooltipStyle ? (
<span id={tooltipId} className={styles.primaryNavTooltip} role="tooltip" style={tooltipStyle}>
{item.label}
</span>
) : null}
{collapsed && tooltipStyle
? createPortal(
<span id={tooltipId} className={styles.primaryNavTooltip} role="tooltip" style={tooltipStyle}>
{item.label}
</span>,
document.body,
)
: null}
</button>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { PanelLeftOpen, Plus, Search, Server } from "lucide-react";
import { PanelLeftOpen, Plus, Search } from "lucide-react";
import {
SidebarAlertTriangleIcon,
SidebarBoxIcon,
SidebarGrid07Icon,
SidebarLaptopIcon,
SidebarListUnordered4Icon,
SidebarMcpIcon,
SidebarMessageIcon,
SidebarPuzzlePiece02Icon,
SidebarRobotIcon,
Expand Down Expand Up @@ -304,7 +305,7 @@ export function WorkspaceSidebar({
active: activeContextSectionId === WorkspaceContextSectionIds.mcpServers,
badge: badgeCount(hub?.mcpServers?.length),
groupId: WorkspaceContextSectionIds.mcpServers,
icon: navigationIcon(Server),
icon: navigationIcon(SidebarMcpIcon),
id: "mcp-servers",
label: t("resourcesMCPLabel"),
onSelect: () => {
Expand Down
26 changes: 0 additions & 26 deletions web/app/src/shared/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,9 @@
}

:root {
--app-ui-floating-layer-scale: 1;
--app-ui-viewport-height: 100dvh;
}

@supports (zoom: 80%) {
:root {
--app-ui-floating-layer-scale: 125%;
--app-ui-viewport-height: 125dvh;
--text-xs-size: 13px;
--text-sm-size: 15px;
--text-md-size: 17px;
--text-lg-size: 19px;
--text-xl-size: 21px;
--text-2xl-size: 25px;
--text-3xl-size: 31px;
--text-4xl-size: 37px;
--text-5xl-size: 49px;
--text-6xl-size: 61px;
--text-7xl-size: 73px;
font-size-adjust: 0.65;
zoom: 80%;
}
}

/* Radix positions poppers in visual viewport coordinates, so cancel the root zoom on its positioning wrapper. */
[data-radix-popper-content-wrapper] {
zoom: var(--app-ui-floating-layer-scale);
}

html,
body,
#root {
Expand Down
24 changes: 8 additions & 16 deletions web/app/tests/components/ProfilePreviewPopover.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@ function t(key: string): string {
}

describe("ProfilePreviewPopover", () => {
it("keeps the preview outside its anchor when the page is zoomed", () => {
const previousZoom = document.documentElement.style.zoom;
document.documentElement.style.zoom = "0.8";
it("keeps the preview outside its anchor in normal viewport coordinates", () => {
const anchorRect = { top: 120, right: 502, bottom: 152, left: 470 };

const view = render(
render(
<ProfilePreviewPopover
previewRef={createRef<HTMLElement>()}
agent={{ id: "agent-1", name: "Builder", role: "worker", status: "running" }}
Expand All @@ -52,19 +50,13 @@ describe("ProfilePreviewPopover", () => {
/>,
);

try {
const preview = screen.getByRole("dialog", { name: "Profile preview" });
const scale = 0.8;
const visualLeft = Number.parseFloat(preview.style.left) * scale;
const visualTop = Number.parseFloat(preview.style.top) * scale;
const preview = screen.getByRole("dialog", { name: "Profile preview" });
const left = Number.parseFloat(preview.style.left);
const top = Number.parseFloat(preview.style.top);

expect(visualLeft).toBe(anchorRect.right + 12);
expect(visualTop).toBe(anchorRect.top - 12);
expect(visualLeft).toBeGreaterThan(anchorRect.right);
} finally {
view.unmount();
document.documentElement.style.zoom = previousZoom;
}
expect(left).toBe(anchorRect.right + 12);
expect(top).toBe(anchorRect.top - 12);
expect(left).toBeGreaterThan(anchorRect.right);
});

it("shows compact agent runtime/provider/model fields with reasoning in model", () => {
Expand Down
19 changes: 8 additions & 11 deletions web/app/tests/shared/appScale.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,19 @@ function readSource(path: string): string {
}

describe("default app scale", () => {
it("renders the full UI at 80 percent while preserving the scaled viewport height", () => {
it("renders the full UI at normal viewport scale", () => {
const globals = readSource("src/shared/styles/globals.css");
const workspace = readSource("src/pages/WorkspacePage/components/WorkspaceComponents.css");
const workspaceLayout = readSource("src/pages/WorkspacePage/components/WorkspaceLayout/WorkspaceLayout.module.css");
const settings = readSource("src/pages/SettingsPage/SettingsPage.module.css");

expect(globals).toContain("@supports (zoom: 80%)");
expect(globals).toContain("--app-ui-floating-layer-scale: 125%;");
expect(globals).toContain("--app-ui-viewport-height: 125dvh;");
expect(globals).toContain("--text-xs-size: 13px;");
expect(globals).toContain("--text-sm-size: 15px;");
expect(globals).toContain("--text-md-size: 17px;");
expect(globals).toContain("font-size-adjust: 0.65;");
expect(globals).toContain("zoom: 80%;");
expect(globals).toContain("[data-radix-popper-content-wrapper]");
expect(globals).toContain("zoom: var(--app-ui-floating-layer-scale);");
expect(globals).toContain("--app-ui-viewport-height: 100dvh;");
expect(globals).not.toContain("--app-ui-floating-layer-scale");
expect(globals).not.toContain("@supports (zoom: 80%)");
expect(globals).not.toContain("--app-ui-viewport-height: 125dvh;");
expect(globals).not.toContain("font-size-adjust: 0.65;");
expect(globals).not.toContain("zoom: 80%;");
expect(globals).not.toContain("[data-radix-popper-content-wrapper]");
expect(globals).toContain("font-size: var(--text-md-size);");
expect(globals).toContain("font-family: var(--font-sans);");
expect(workspace).toContain("height: var(--app-ui-viewport-height);");
Expand Down
Loading