From 3bf8cfd12703f70833ea8cbf4b8210351c4d0d32 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 13 Jun 2026 23:10:16 +0000 Subject: [PATCH] Tighten issue service quick controls Co-Authored-By: Paperclip --- ui/src/components/IssueProperties.test.tsx | 10 ++++-- ui/src/components/IssueProperties.tsx | 2 ++ .../WorkspaceRuntimeControls.test.tsx | 6 +++- .../components/WorkspaceRuntimeControls.tsx | 34 ++++++++++++++----- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/ui/src/components/IssueProperties.test.tsx b/ui/src/components/IssueProperties.test.tsx index 0de4bb640cb..e6da3966614 100644 --- a/ui/src/components/IssueProperties.test.tsx +++ b/ui/src/components/IssueProperties.test.tsx @@ -772,13 +772,17 @@ describe("IssueProperties", () => { }); await flush(); - expect(container.querySelector(`a[href="${serviceUrl}"]`)).not.toBeNull(); + const serviceLink = container.querySelector(`a[href="${serviceUrl}"]`); + expect(serviceLink).not.toBeNull(); + expect(serviceLink?.className).toContain("sm:self-start"); + expect(serviceLink?.className).not.toContain("sm:self-end"); expect((container.textContent ?? "").indexOf("Workspace")).toBeLessThan( (container.textContent ?? "").indexOf("Service"), ); - const stopButton = Array.from(container.querySelectorAll("button")) - .find((button) => button.textContent?.trim() === "Stop"); + const stopButton = container.querySelector('button[aria-label="Stop"]'); expect(stopButton).not.toBeUndefined(); + expect(stopButton?.getAttribute("data-size")).toBe("icon-xs"); + expect(stopButton?.getAttribute("data-variant")).toBe("outline"); await act(async () => { stopButton!.dispatchEvent(new MouseEvent("click", { bubbles: true })); diff --git a/ui/src/components/IssueProperties.tsx b/ui/src/components/IssueProperties.tsx index 699168b3108..d30ca00a5cc 100644 --- a/ui/src/components/IssueProperties.tsx +++ b/ui/src/components/IssueProperties.tsx @@ -2220,6 +2220,8 @@ export function IssueProperties({ pendingRequest={pendingWorkspaceRuntimeAction} onAction={(request) => controlWorkspaceRuntime.mutate(request)} square + align="start" + iconOnly /> {runtimeActionMessage ? ( {runtimeActionMessage} diff --git a/ui/src/components/WorkspaceRuntimeControls.test.tsx b/ui/src/components/WorkspaceRuntimeControls.test.tsx index 606556de5dd..6442c676a9c 100644 --- a/ui/src/components/WorkspaceRuntimeControls.test.tsx +++ b/ui/src/components/WorkspaceRuntimeControls.test.tsx @@ -1,6 +1,6 @@ // @vitest-environment jsdom -import { act } from "react"; +import { flushSync } from "react-dom"; import { createRoot } from "react-dom/client"; import type { WorkspaceRuntimeService } from "@paperclipai/shared"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -14,6 +14,10 @@ import { // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; +function act(callback: () => void) { + flushSync(callback); +} + function createRuntimeService(overrides: Partial = {}): WorkspaceRuntimeService { return { id: overrides.id ?? "service-1", diff --git a/ui/src/components/WorkspaceRuntimeControls.tsx b/ui/src/components/WorkspaceRuntimeControls.tsx index cc03903e130..46ae26013a2 100644 --- a/ui/src/components/WorkspaceRuntimeControls.tsx +++ b/ui/src/components/WorkspaceRuntimeControls.tsx @@ -226,12 +226,14 @@ function CommandActionButtons({ pendingRequest, onAction, square, + iconOnly, }: { item: WorkspaceRuntimeControlItem; isPending: boolean; pendingRequest: WorkspaceRuntimeControlRequest | null | undefined; onAction: (request: WorkspaceRuntimeControlRequest) => void; square?: boolean; + iconOnly?: boolean; }) { const actions: WorkspaceRuntimeAction[] = item.kind === "job" @@ -241,7 +243,7 @@ function CommandActionButtons({ : ["start"]; return ( -
+
{actions.map((action) => { const request = buildRequest(item, action); const Icon = action === "stop" ? Square : action === "restart" ? RotateCcw : Play; @@ -261,17 +263,19 @@ function CommandActionButtons({ return ( ); })} @@ -289,6 +293,7 @@ function CommandSection({ pendingRequest, onAction, square, + iconOnly, }: { title: string; description: string; @@ -299,6 +304,7 @@ function CommandSection({ pendingRequest: WorkspaceRuntimeControlRequest | null | undefined; onAction: (request: WorkspaceRuntimeControlRequest) => void; square?: boolean; + iconOnly?: boolean; }) { return (
@@ -330,6 +336,7 @@ function CommandSection({ pendingRequest={pendingRequest} onAction={onAction} square={square} + iconOnly={iconOnly} />
@@ -466,24 +473,29 @@ export function WorkspaceRuntimeQuickControls({ pendingRequest = null, onAction, square, + align = "end", + iconOnly = false, }: { sections: WorkspaceRuntimeControlSections; isPending?: boolean; pendingRequest?: WorkspaceRuntimeControlRequest | null; onAction: (request: WorkspaceRuntimeControlRequest) => void; square?: boolean; + align?: "start" | "end"; + iconOnly?: boolean; }) { const controlItems = sections.services.length > 0 ? sections.services : sections.otherServices; const serviceUrl = getRunningRuntimeServiceUrl(sections); + const alignEnd = align === "end"; if (controlItems.length === 0 && !serviceUrl) return null; return ( -
+
{controlItems.length > 0 ? ( -
+
{controlItems.map((item) => ( -
+
{controlItems.length > 1 ? ( {item.title} ) : null} @@ -493,6 +505,7 @@ export function WorkspaceRuntimeQuickControls({ pendingRequest={pendingRequest} onAction={onAction} square={square} + iconOnly={iconOnly} />
))} @@ -503,7 +516,10 @@ export function WorkspaceRuntimeQuickControls({ href={serviceUrl} target="_blank" rel="noreferrer" - className="inline-flex min-w-0 items-center gap-1 self-start break-all text-xs text-muted-foreground hover:text-foreground hover:underline sm:self-end" + className={cn( + "inline-flex min-w-0 items-center gap-1 self-start break-all text-xs text-muted-foreground hover:text-foreground hover:underline", + alignEnd ? "sm:self-end" : "sm:self-start", + )} > {serviceUrl}