From 622376e995b7063020f1beb0be0b55256e1f2bf8 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:05:42 -0500 Subject: [PATCH] fix(ui): remove action buttons from agent list (#13101) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The agents list helps operators scan names, roles, and status. > - Each row also shows task, heartbeat, trace, pause, and overflow controls. > - These controls add a second line to every row and crowd the list. > - This pull request removes the action bar from list rows. > - Operators can still use agent detail pages for those actions. ## Linked Issues or Issue Description Related: #7543 introduced list actions. #13011 moved the row actions onto a separate line. No duplicate fix was found. **What happened?** The `/agents/all` list shows Assign Task, Run Heartbeat, Run with provider trace, Pause or Resume, and an overflow menu below each agent. **Expected behavior** The agent list should show agent information without this action bar. **Steps to reproduce** 1. Open `/agents/all` in an organization with agents. 2. Select the list view. 3. Inspect the controls below each agent name. **Paperclip version or commit** Reproduced in source at fac07b42a. **Deployment mode** Board UI. The change is independent of deployment mode. ## What Changed - Removed AgentActionButtons from both agent list implementations, including filtered lists. - Removed the unused board-access query and trace permission calculation. - Kept built-in setup controls, status, and membership actions. - Enabled the normal Run now control on the streamlined agent detail page so operators can still trigger a heartbeat there. - Updated the existing mobile row test and added active/paused row coverage for both list implementations. ## Verification - Passed: `pnpm check:token-gates`. - Passed: `pnpm exec vitest run ui/src/pages/Agents.test.tsx ui/src/components/AgentActionButtons.test.tsx` (27 tests). - Passed: `pnpm -r typecheck` and `pnpm build`. After the review fixes, the UI typecheck, UI build, token gates, and targeted tests passed again. - Full CI passed on 6d527b02a, including all server/workspace test shards, browser tests, typecheck, build, and canary dry run. - Local `pnpm test:run` passed 520 server suites (7,234 tests). The later UI batch had two Inbox failures under load; an isolated Inbox rerun passed all 27 tests. Stopped the redundant local full run after all CI shards passed. - The CI build first hit an unrelated port collision in a Codex-credentials test. One rerun passed runner verification and the full build. - Greptile: 5/5. Both review threads are resolved. - Manual review: open the agent list and verify the action bar is absent. Open an agent to access its actions. ## Risks - Low risk. This changes agent list rendering and restores the normal detail-page run control. Operators must open an agent to use the removed shortcuts. - No API, database, or permission contracts change. ## Model Used - OpenAI GPT-6 (Codex). Exact deployment model ID and context window size are not exposed in this session. Used reasoning, repository inspection, code editing, and command execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- ui/src/pages/AgentDetail.tsx | 1 - ui/src/pages/Agents.production.tsx | 17 ----------- ui/src/pages/Agents.test.tsx | 49 ++++++++++++++++++++++++------ ui/src/pages/Agents.tsx | 40 +++--------------------- 4 files changed, 44 insertions(+), 63 deletions(-) diff --git a/ui/src/pages/AgentDetail.tsx b/ui/src/pages/AgentDetail.tsx index f9d92dabe5..0d09353efd 100644 --- a/ui/src/pages/AgentDetail.tsx +++ b/ui/src/pages/AgentDetail.tsx @@ -1264,7 +1264,6 @@ export function AgentDetail() { agent={agent} companyId={resolvedCompanyId} assignLabel="Assign Task" - showRun={false} showStatus={false} canRunWithProviderTrace={canUseProviderTrace} actionsDisabled={agentAction.isPending} diff --git a/ui/src/pages/Agents.production.tsx b/ui/src/pages/Agents.production.tsx index 8e519f538b..7ba12ed9e4 100644 --- a/ui/src/pages/Agents.production.tsx +++ b/ui/src/pages/Agents.production.tsx @@ -13,7 +13,6 @@ import { useSidebar } from "../context/SidebarContext"; import { queryKeys } from "../lib/queryKeys"; import { isPlatformManagedEnvironment } from "../lib/managed-sandbox-environment"; import { AgentStatusBadge, AgentStatusCapsule } from "../components/StatusBadge"; -import { AgentActionButtons } from "../components/AgentActionButtons"; import { MembershipAction } from "../components/MembershipAction"; import { StarToggle } from "../components/StarToggle"; import { EntityRow } from "../components/EntityRow"; @@ -442,22 +441,6 @@ export function Agents() { - {/* Row actions mirror the agent detail page; stop the click - from bubbling to the row link so buttons don't navigate. - Hidden on mobile so the agent name keeps room to render. */} -
{ - e.preventDefault(); - e.stopPropagation(); - }} - > - -
({ @@ -357,6 +358,40 @@ describe("Agents", () => { vi.clearAllMocks(); }); + it.each([ + ["streamlined", Agents], + ["production", ProductionAgents], + ] as const)("omits the action bar from %s agent list rows", async (_mode, AgentList) => { + mockAgentsApi.list.mockResolvedValue([ + makeAgent({ name: "Alpha", status: "active" }), + makeAgent({ id: "agent-paused", name: "Paused agent", status: "paused" }), + ]); + root = createRoot(container); + await act(async () => { + root!.render( + + + + + , + ); + }); + await flushReact(); + await flushReact(); + const listToggle = container.querySelector('button[aria-label="List view"]'); + await act(async () => { listToggle?.click(); }); + await flushReact(); + + for (const name of ["Alpha", "Paused agent"]) { + const row = findAgentRow(container, name); + expect(row).not.toBeNull(); + expect(row?.getAttribute("href")).toMatch(/^\/agents\//); + expect(row?.querySelector('button[aria-label^="Open actions for"]')).toBeNull(); + const buttons = Array.from(row?.querySelectorAll("button") ?? []).map((button) => button.textContent); + expect(buttons).not.toEqual(expect.arrayContaining([expect.stringMatching(/Assign Task|Run Heartbeat|Run with provider trace|Pause|Resume/)])); + } + }); + it("shows the configured model beside the adapter on the all agents page", async () => { root = createRoot(container); await act(async () => { @@ -479,15 +514,11 @@ describe("Agents", () => { expect(subtitle).toBeDefined(); expect(subtitle?.classList.contains("truncate")).toBe(true); const actions = row?.querySelector('button[aria-label="Open actions for Paperclip Engineer With A Much Longer Display Name"]'); - expect(actions).not.toBeNull(); - // Neither the action button nor its ancestors may hide the mobile menu. - for (let node = actions; node && node !== row; node = node.parentElement) { - expect(node.classList.contains("hidden")).toBe(false); - } - await act(async () => { (actions as HTMLButtonElement).click(); }); - await flushReact(); - expect(document.body.textContent).toContain("Duplicate"); - expect(document.body.textContent).toContain("Terminate"); + expect(actions).toBeNull(); + expect(row?.textContent).not.toContain("Assign Task"); + expect(row?.textContent).not.toContain("Run Heartbeat"); + expect(row?.textContent).not.toContain("Run with provider trace"); + expect(row?.textContent).not.toContain("Pause"); }); it("uses the built-in agents route segment as the built-in filter", async () => { diff --git a/ui/src/pages/Agents.tsx b/ui/src/pages/Agents.tsx index 9c83b9ec01..1c0584c05a 100644 --- a/ui/src/pages/Agents.tsx +++ b/ui/src/pages/Agents.tsx @@ -6,7 +6,6 @@ import { builtInAgentsApi, type BuiltInAgentState } from "../api/builtInAgents"; import { environmentsApi } from "../api/environments"; import { heartbeatsApi } from "../api/heartbeats"; import { instanceSettingsApi } from "../api/instanceSettings"; -import { accessApi } from "../api/access"; import { useCompany } from "../context/CompanyContext"; import { useDialogActions } from "../context/DialogContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; @@ -15,7 +14,6 @@ import { useStreamlinedUiEnabled } from "../hooks/useStreamlinedUiEnabled"; import { queryKeys } from "../lib/queryKeys"; import { isPlatformManagedEnvironment } from "../lib/managed-sandbox-environment"; import { AgentStatusBadge, AgentStatusCapsule } from "../components/StatusBadge"; -import { AgentActionButtons } from "../components/AgentActionButtons"; import { MembershipAction } from "../components/MembershipAction"; import { StarToggle } from "../components/StarToggle"; import { EntityRow } from "../components/EntityRow"; @@ -211,15 +209,6 @@ export function Agents({ initialView = "list" }: { initialView?: AgentsView } = setView(streamlinedUiEnabled ? initialView : "org"); }, [initialView, streamlinedUiEnabled]); - const { data: boardAccess } = useQuery({ - queryKey: queryKeys.access.currentBoardAccess, - queryFn: () => accessApi.getCurrentBoardAccess(), - retry: false, - }); - const canUseProviderTrace = - boardAccess?.source === "local_implicit" || - boardAccess?.isInstanceAdmin === true; - const { data: instanceSettings } = useQuery({ queryKey: queryKeys.instance.settings, queryFn: () => instanceSettingsApi.get(), @@ -411,32 +400,11 @@ export function Agents({ initialView = "list" }: { initialView?: AgentsView } = ) : ( )} - secondaryRow={ -
- {builtInCluster && ( -
- {builtInCluster} -
- )} - {/* Actions have their own wrapping line, so names keep their width. */} -
{ - e.preventDefault(); - e.stopPropagation(); - }} - > - -
+ secondaryRow={builtInCluster && ( +
+ {builtInCluster}
- } + )} meta={
{builtInCluster && (