From 2dda0eafbb33d9b723aef2a19c5568a39099849b Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Thu, 27 Aug 2026 11:50:13 -0700 Subject: [PATCH] feat(desktop): add review check debug harness Signed-off-by: Taylor Ho --- .../projects/ui/ProjectReviewChecks.tsx | 127 ++++-------------- .../projects/ui/ProjectReviewDebugHarness.tsx | 98 ++++++++++++++ desktop/tests/e2e/project-pr-review.spec.ts | 37 ++++- 3 files changed, 157 insertions(+), 105 deletions(-) create mode 100644 desktop/src/features/projects/ui/ProjectReviewDebugHarness.tsx diff --git a/desktop/src/features/projects/ui/ProjectReviewChecks.tsx b/desktop/src/features/projects/ui/ProjectReviewChecks.tsx index b11d7e442c..2dd16ef78d 100644 --- a/desktop/src/features/projects/ui/ProjectReviewChecks.tsx +++ b/desktop/src/features/projects/ui/ProjectReviewChecks.tsx @@ -1,7 +1,6 @@ import { Bot, CheckCircle2, - ChevronDown, CircleAlert, LoaderCircle, Play, @@ -49,6 +48,10 @@ import type { ProjectsConversationOpener } from "@/features/projects/lib/project import { pullRequestShareLink } from "@/features/projects/lib/projectShareLinks"; import type { ProjectPullRequest, Repository } from "@/features/projects/hooks"; import { ProjectReviewCheckResultCard } from "@/features/projects/ui/ProjectReviewCheckResultCard"; +import { + ProjectReviewDebugHarness, + type ReviewCheckAgent, +} from "@/features/projects/ui/ProjectReviewDebugHarness"; import { useIdentityQuery } from "@/shared/api/hooks"; import { listManagedAgents } from "@/shared/api/tauri"; import { revalidateRelayAgents } from "@/shared/api/tauriRelayAgents"; @@ -63,13 +66,6 @@ import { import { normalizePubkey } from "@/shared/lib/pubkey"; import { Badge } from "@/shared/ui/badge"; import { Button } from "@/shared/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuTrigger, -} from "@/shared/ui/dropdown-menu"; type ReviewCheckStatus = "idle" | "running" | "completed" | "failed"; @@ -89,13 +85,6 @@ type ReviewCheckRun = { type ReviewCheckRuns = Record; -type ReviewCheckAgent = { - pubkey: string; - name: string; - isManaged: boolean; - isActive: boolean; -}; - function isReviewCheckStatus(value: unknown): value is ReviewCheckStatus { return ( value === "idle" || @@ -457,66 +446,6 @@ function CheckStatus({ return null; } -function AgentPicker({ - candidates, - disabled, - onSelect, - selected, -}: { - candidates: ReviewCheckAgent[]; - disabled: boolean; - onSelect: (pubkey: string) => void; - selected: ReviewCheckAgent | null; -}) { - return ( - - - - - - - {candidates.map((candidate) => ( - - {candidate.name} - - {candidate.isManaged - ? candidate.isActive - ? "Running here" - : "Can start here" - : candidate.isActive - ? "Online on relay" - : "Offline"} - - - ))} - - - - ); -} - export function ProjectReviewChecks({ project, pullRequest, @@ -548,6 +477,9 @@ export function ProjectReviewChecks({ const [pendingCheckIds, setPendingCheckIds] = React.useState>( () => new Set(), ); + const [selectedAgentPubkey, setSelectedAgentPubkey] = React.useState< + string | null + >(null); const targetCommit = pullRequest.commit ?? pullRequest.initialCommit; const diffRepoUrl = React.useMemo( () => @@ -566,6 +498,7 @@ export function ProjectReviewChecks({ ); setRuns(normalizeStoredRuns(stored)); setPendingCheckIds(new Set()); + setSelectedAgentPubkey(null); }, [storageKey]); const updateRun = React.useCallback( @@ -593,17 +526,6 @@ export function ProjectReviewChecks({ [], ); - const assignAgent = React.useCallback( - (checkId: string, pubkey: string) => { - if (!storageKey) return; - updateRun(storageKey, checkId, () => ({ - agentPubkey: normalizePubkey(pubkey), - status: "idle", - })); - }, - [storageKey, updateRun], - ); - const recordResult = React.useCallback( ( checkId: string, @@ -806,13 +728,19 @@ export function ProjectReviewChecks({ const defaultAgent = reviewCheckAgents.candidates.find((candidate) => candidate.isActive) ?? null; + const selectedAgent = selectedAgentPubkey + ? (reviewCheckAgents.candidates.find( + (candidate) => + candidate.isActive && + normalizePubkey(candidate.pubkey) === + normalizePubkey(selectedAgentPubkey), + ) ?? defaultAgent) + : defaultAgent; return (
- The first running agent is selected automatically; you can choose a - different local managed agent or relay agent authorized for your - signed-in identity. Definitions come from the target branch + Definitions come from the target branch {checkDefinitions.source === "project" ? ` (${PROJECT_REVIEW_CHECKS_CONFIG_PATH})` : checkDefinitions.source === "starter" @@ -826,13 +754,13 @@ export function ProjectReviewChecks({ agentPubkey: null, status: "idle" as const, }; - const selectedAgent = run.agentPubkey + const runAgent = run.agentPubkey ? (reviewCheckAgents.candidates.find( (candidate) => normalizePubkey(candidate.pubkey) === normalizePubkey(run.agentPubkey ?? ""), ) ?? null) - : defaultAgent; + : null; const isPending = pendingCheckIds.has(check.id); const isStale = run.status === "completed" && run.targetCommit !== targetCommit; @@ -870,12 +798,6 @@ export function ProjectReviewChecks({

- assignAgent(check.id, pubkey)} - selected={selectedAgent} - />
) : null} +
); } diff --git a/desktop/src/features/projects/ui/ProjectReviewDebugHarness.tsx b/desktop/src/features/projects/ui/ProjectReviewDebugHarness.tsx new file mode 100644 index 0000000000..b7d0326c3d --- /dev/null +++ b/desktop/src/features/projects/ui/ProjectReviewDebugHarness.tsx @@ -0,0 +1,98 @@ +import { Bug } from "lucide-react"; + +import { Button } from "@/shared/ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover"; + +export type ReviewCheckAgent = { + pubkey: string; + name: string; + isManaged: boolean; + isActive: boolean; +}; + +function agentAvailabilityLabel(agent: ReviewCheckAgent) { + return agent.isManaged ? "Running here" : "Online on relay"; +} + +export function ProjectReviewDebugHarness({ + candidates, + hasError, + isLoading, + onSelect, + selected, +}: { + candidates: ReviewCheckAgent[]; + hasError: boolean; + isLoading: boolean; + onSelect: (pubkey: string) => void; + selected: ReviewCheckAgent | null; +}) { + const activeCandidates = candidates.filter((candidate) => candidate.isActive); + const offlineAgentCount = candidates.length - activeCandidates.length; + + return ( +
+ + + + + +
+

Review debug harness

+

+ Choose the agent used for new checks. +

+
+ +
+
+
+ ); +} diff --git a/desktop/tests/e2e/project-pr-review.spec.ts b/desktop/tests/e2e/project-pr-review.spec.ts index 202bc7b4af..84126aec8f 100644 --- a/desktop/tests/e2e/project-pr-review.spec.ts +++ b/desktop/tests/e2e/project-pr-review.spec.ts @@ -169,8 +169,10 @@ test("review checks dispatch to an agent authorized for the relay identity", asy await expect(checks).toBeVisible(); await expect(checks.getByTestId("project-review-check")).toHaveCount(3); const interfaceCheck = checks.getByTestId("project-review-check").first(); - await interfaceCheck.getByTestId("project-review-check-agent-picker").click(); - await page.getByRole("menuitemradio", { name: /charlie/i }).click(); + await page.getByTestId("project-review-debug-harness-trigger").click(); + await page + .getByTestId("project-review-debug-agent-select") + .selectOption(RELAY_REVIEW_AGENT_PUBKEY); await interfaceCheck .getByRole("button", { name: "Run", exact: true }) .click(); @@ -350,6 +352,13 @@ test("review checks dispatch to a running local managed agent", async ({ respondTo: "allowlist", respondToAllowlist: [], }, + ...Array.from({ length: 16 }, (_, index) => ({ + pubkey: (index + 1).toString(16).padStart(64, "0"), + name: `Stopped prototype ${index + 1}`, + status: "stopped" as const, + respondTo: "allowlist" as const, + respondToAllowlist: [], + })), ], }); await openBuzzProject(page); @@ -362,12 +371,28 @@ test("review checks dispatch to a running local managed agent", async ({ const checks = page.getByTestId("project-review-checks"); const interfaceCheck = checks.getByTestId("project-review-check").first(); + const debugHarness = page.getByTestId("project-review-debug-harness-trigger"); + await expect(debugHarness).toHaveCount(1); + await expect(debugHarness).toHaveAccessibleName( + "Review check debug harness, agent Roof", + ); + await debugHarness.click(); await expect( - checks.getByTestId("project-review-check-agent-picker"), - ).toHaveCount(3); + page.getByTestId("project-review-debug-agent-select"), + ).toHaveValue(LOCAL_REVIEW_AGENT_PUBKEY); await expect( - interfaceCheck.getByTestId("project-review-check-agent-picker"), - ).toContainText("Roof"); + page + .getByTestId("project-review-debug-agent-select") + .locator("option", { hasText: "Stopped prototype" }), + ).toHaveCount(0); + await expect( + page.getByTestId("project-review-debug-offline-agent-count"), + ).toHaveText("17 offline agents"); + expect( + (await page.getByTestId("project-review-debug-harness-menu").boundingBox()) + ?.height, + ).toBeLessThan(220); + await page.keyboard.press("Escape"); const revalidationCountBefore = await page.evaluate( () => window.__BUZZ_E2E_COMMANDS__?.filter(