From a5eda7a2ac5fc633a0ef5e4f89d070cd1a81cad1 Mon Sep 17 00:00:00 2001 From: kenny lopez Date: Thu, 30 Jul 2026 06:36:58 +0100 Subject: [PATCH 1/2] fix: align responsive agent views Signed-off-by: kenny lopez --- desktop/src/features/agents/ui/AgentsView.tsx | 107 +++++++++++++----- .../src/features/agents/ui/TeamsSection.tsx | 6 +- .../agents/ui/UnifiedAgentsSection.tsx | 8 +- desktop/tests/e2e/agents.spec.ts | 105 +++++++++++++++++ 4 files changed, 189 insertions(+), 37 deletions(-) diff --git a/desktop/src/features/agents/ui/AgentsView.tsx b/desktop/src/features/agents/ui/AgentsView.tsx index f24a3c06d7..d9154b8c46 100644 --- a/desktop/src/features/agents/ui/AgentsView.tsx +++ b/desktop/src/features/agents/ui/AgentsView.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { OctagonX, Settings2 } from "lucide-react"; +import { EllipsisVertical, OctagonX, Settings2 } from "lucide-react"; import { consumePendingSnapshotImport, subscribeSnapshotImport, @@ -20,10 +20,7 @@ import { SecretRevealDialog } from "./SecretRevealDialog"; import { TeamDeleteDialog } from "./TeamDeleteDialog"; import { TeamDialog } from "./TeamDialog"; import { TeamsSection } from "./TeamsSection"; -import { - AGENT_CARD_GRID_COLUMNS_CLASS, - UnifiedAgentsSection, -} from "./UnifiedAgentsSection"; +import { UnifiedAgentsSection } from "./UnifiedAgentsSection"; import { useManagedAgentActions } from "./useManagedAgentActions"; import { usePersonaActions } from "./usePersonaActions"; import { useTeamActions } from "./useTeamActions"; @@ -32,6 +29,12 @@ import { useBakedBuildEnvQuery } from "@/features/agents/hooks"; import { isManagedAgentActive } from "@/features/agents/lib/managedAgentControlActions"; import { useGlobalAgentConfig } from "@/features/agents/useGlobalAgentConfig"; import { Button } from "@/shared/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/shared/ui/dropdown-menu"; import { PageHeader } from "@/shared/ui/PageHeader"; import { getInheritedAgentDefaults } from "./bakedEnvHelpers"; @@ -44,6 +47,7 @@ export function AgentsView() { const personas = usePersonaActions(); const teamImportInputRef = React.useRef(null); const aiDefaultsTriggerRef = React.useRef(null); + const compactActionsTriggerRef = React.useRef(null); const [isAiDefaultsOpen, setIsAiDefaultsOpen] = React.useState(false); // Exclusivity: create never sets `personaDialogState` (edit/dup/import do), // so the create-mode and definition-edit AgentDialog mounts never coexist. @@ -113,43 +117,86 @@ export function AgentsView() { <>
- - {runningAgentCount > 0 ? ( + <> +
- ) : null} -
+ {runningAgentCount > 0 ? ( + + ) : null} +
+ + + + + + + { + aiDefaultsTriggerRef.current = + compactActionsTriggerRef.current; + setIsAiDefaultsOpen(true); + }} + > + + {hasSavedAgentDefaults + ? "Agent defaults" + : "Set agent defaults"} + + {runningAgentCount > 0 ? ( + { + void agents.handleBulkStopRunning(); + }} + > + + Stop running agents + + ) : null} + + + } description="Set up and manage your agents." title="Agents" /> -
+
{isLoading ? ( -
+
+
{teams.map((team) => { const resolution = resolveTeamPersonas(team, personas); const missingPersonaCount = resolution.missingPersonaCount; diff --git a/desktop/src/features/agents/ui/UnifiedAgentsSection.tsx b/desktop/src/features/agents/ui/UnifiedAgentsSection.tsx index 9bbe3feef7..19a5ef1171 100644 --- a/desktop/src/features/agents/ui/UnifiedAgentsSection.tsx +++ b/desktop/src/features/agents/ui/UnifiedAgentsSection.tsx @@ -68,7 +68,7 @@ type UnifiedAgentsSectionProps = { const AGENT_CARD_COLUMN_CLASS = "w-full"; export const AGENT_CARD_GRID_COLUMNS_CLASS = "grid-cols-[repeat(auto-fill,minmax(220px,240px))]"; -const AGENT_CARD_GRID_CLASS = `${AGENT_CARD_COLUMN_CLASS} ${AGENT_CARD_GRID_COLUMNS_CLASS} grid justify-start gap-3`; +export const IDENTITY_CARD_GRID_CLASS = `${AGENT_CARD_COLUMN_CLASS} ${AGENT_CARD_GRID_COLUMNS_CLASS} grid justify-start gap-3 [@container(max-width:40rem)]:justify-center`; export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) { const { @@ -153,7 +153,7 @@ export function UnifiedAgentsSection(props: UnifiedAgentsSectionProps) { {!isLoading ? (
-
+
{groups.map((group) => { const profileAgent = pickProfileAgent(group.agents); return ( @@ -479,7 +479,7 @@ function NewAgentCard({ function LoadingSkeleton() { return ( -
+
({agents.length}) {!isCollapsed ? ( -
+
{agents.map((agent) => ( { ).toBeVisible(); }); +test("team cards follow the agents grid alignment at compact widths", async ({ + page, +}) => { + await installMockBridge(page, { + personas: [ + { + id: "custom:team-layout", + displayName: "Team layout agent", + systemPrompt: "A test agent for team layout alignment.", + }, + ], + teams: [ + { + id: "team-layout", + name: "Team layout", + personaIds: ["custom:team-layout"], + }, + ], + }); + await gotoApp(page); + await page.getByTestId("open-agents-view").click(); + + const agentsContent = page.getByTestId("agents-page-content"); + const firstAgentCard = page.getByTestId( + "persona-agent-row-custom:team-layout", + ); + const firstTeamCard = page.getByTestId("team-card-team-layout"); + const agentGrid = firstAgentCard.locator("xpath=.."); + const teamGrid = firstTeamCard.locator("xpath=.."); + const firstAgentGridCard = agentGrid.locator(":scope > *").first(); + const firstTeamGridCard = teamGrid.locator(":scope > *").first(); + + await agentsContent.evaluate((element) => { + (element as HTMLElement).style.width = "650px"; + }); + const wideAgentBox = await firstAgentGridCard.boundingBox(); + const wideTeamBox = await firstTeamGridCard.boundingBox(); + expect(wideAgentBox).not.toBeNull(); + expect(wideTeamBox).not.toBeNull(); + expect(Math.abs((wideAgentBox?.x ?? 0) - (wideTeamBox?.x ?? 0))).toBeLessThan( + 1, + ); + + await agentsContent.evaluate((element) => { + (element as HTMLElement).style.width = "600px"; + }); + await expect + .poll(async () => (await firstAgentGridCard.boundingBox())?.x ?? 0) + .toBeGreaterThan(wideAgentBox?.x ?? 0); + + const compactAgentBox = await firstAgentGridCard.boundingBox(); + const compactTeamBox = await firstTeamGridCard.boundingBox(); + expect( + Math.abs((compactAgentBox?.x ?? 0) - (compactTeamBox?.x ?? 0)), + ).toBeLessThan(1); +}); + test("team cards use the thread-style overlapping avatar stack", async ({ page, }) => { @@ -634,6 +691,54 @@ test("unconfigured agent defaults use the setup label", async ({ page }) => { ); }); +test("moves agent actions into an overflow menu in a narrow view", async ({ + page, +}) => { + await installMockBridge(page, { + personas: [ + { + id: "custom:compact-actions", + displayName: "Compact actions agent", + isActive: true, + systemPrompt: "A test agent for compact header actions.", + }, + ], + managedAgents: [ + { + name: "Compact actions instance", + personaId: "custom:compact-actions", + pubkey: "cd".repeat(32), + status: "running", + }, + ], + }); + await gotoApp(page); + await page.getByTestId("open-agents-view").click(); + await page.getByTestId("agents-page-content").evaluate((element) => { + (element as HTMLElement).style.width = "650px"; + }); + + await expect(page.getByTestId("agent-defaults-button")).toBeVisible(); + await expect( + page.getByText("Set up and manage your agents.", { exact: true }), + ).toHaveJSProperty("scrollHeight", 24); + + await page.getByTestId("agents-page-content").evaluate((element) => { + (element as HTMLElement).style.width = "600px"; + }); + await expect(page.getByTestId("agent-defaults-button")).toBeHidden(); + await page.getByTestId("agent-actions-menu-trigger").click(); + await expect( + page.getByRole("menuitem", { name: "Set agent defaults" }), + ).toBeVisible(); + await expect( + page.getByRole("menuitem", { name: "Stop running agents" }), + ).toBeVisible(); + + await page.getByRole("menuitem", { name: "Set agent defaults" }).click(); + await expect(page.getByTestId("agent-ai-defaults-dialog")).toBeVisible(); +}); + test("agent catalog chooser order stays stable when selection changes", async ({ page, }) => { From b3244057db547ba085fef1cd96f864b173e6b54a Mon Sep 17 00:00:00 2001 From: kenny lopez Date: Thu, 30 Jul 2026 07:35:23 +0100 Subject: [PATCH 2/2] fix: preserve agent defaults focus Signed-off-by: kenny lopez --- desktop/src/features/agents/ui/AgentsView.tsx | 27 ++++++++++++++----- desktop/tests/e2e/agents.spec.ts | 24 ++++++++++++----- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/desktop/src/features/agents/ui/AgentsView.tsx b/desktop/src/features/agents/ui/AgentsView.tsx index d9154b8c46..3d1673c365 100644 --- a/desktop/src/features/agents/ui/AgentsView.tsx +++ b/desktop/src/features/agents/ui/AgentsView.tsx @@ -47,6 +47,7 @@ export function AgentsView() { const personas = usePersonaActions(); const teamImportInputRef = React.useRef(null); const aiDefaultsTriggerRef = React.useRef(null); + const fullAiDefaultsTriggerRef = React.useRef(null); const compactActionsTriggerRef = React.useRef(null); const [isAiDefaultsOpen, setIsAiDefaultsOpen] = React.useState(false); // Exclusivity: create never sets `personaDialogState` (edit/dup/import do), @@ -57,6 +58,22 @@ export function AgentsView() { personas.prepareCreate(); setIsCreateDialogOpen(true); } + + function openAiDefaults(trigger: HTMLButtonElement | null) { + aiDefaultsTriggerRef.current = trigger; + setIsAiDefaultsOpen(true); + } + + function setAiDefaultsDialogOpen(open: boolean) { + if (!open) { + aiDefaultsTriggerRef.current = + fullAiDefaultsTriggerRef.current?.offsetParent !== null + ? fullAiDefaultsTriggerRef.current + : compactActionsTriggerRef.current; + } + setIsAiDefaultsOpen(open); + } + const teamActions = useTeamActions( { setActionNoticeMessage: agents.setActionNoticeMessage, @@ -126,8 +143,8 @@ export function AgentsView() {
diff --git a/desktop/tests/e2e/agents.spec.ts b/desktop/tests/e2e/agents.spec.ts index 485a652113..8c1c407df1 100644 --- a/desktop/tests/e2e/agents.spec.ts +++ b/desktop/tests/e2e/agents.spec.ts @@ -416,7 +416,7 @@ test("the new agent card offers create, discover, and import", async ({ const cardBoxes = await agentCards.evaluateAll((cards) => cards.map((card) => { const box = card.getBoundingClientRect(); - return { right: box.right, top: box.top }; + return { left: box.left, right: box.right, top: box.top }; }), ); const firstRowTop = Math.min(...cardBoxes.map(({ top }) => top)); @@ -425,12 +425,16 @@ test("the new agent card offers create, discover, and import", async ({ .filter(({ top }) => Math.abs(top - firstRowTop) < 1) .map(({ right }) => right), ); + const leftmostFirstRowCard = Math.min( + ...cardBoxes + .filter(({ top }) => Math.abs(top - firstRowTop) < 1) + .map(({ left }) => left), + ); expect(headerBox).not.toBeNull(); - expect( - Math.abs( - (headerBox?.x ?? 0) + (headerBox?.width ?? 0) - rightmostFirstRowCard, - ), - ).toBeLessThan(1); + expect(Math.abs((headerBox?.x ?? 0) - leftmostFirstRowCard)).toBeLessThan(1); + expect(rightmostFirstRowCard).toBeLessThanOrEqual( + (headerBox?.x ?? 0) + (headerBox?.width ?? 0) + 1, + ); await newAgentCard.click(); await expect( @@ -737,6 +741,14 @@ test("moves agent actions into an overflow menu in a narrow view", async ({ await page.getByRole("menuitem", { name: "Set agent defaults" }).click(); await expect(page.getByTestId("agent-ai-defaults-dialog")).toBeVisible(); + + await page.getByTestId("agents-page-content").evaluate((element) => { + (element as HTMLElement).style.width = "650px"; + }); + await expect(page.getByTestId("agent-defaults-button")).toBeVisible(); + await page.keyboard.press("Escape"); + await expect(page.getByTestId("agent-ai-defaults-dialog")).toHaveCount(0); + await expect(page.getByTestId("agent-defaults-button")).toBeFocused(); }); test("agent catalog chooser order stays stable when selection changes", async ({