diff --git a/packages/shared/src/workspace-commands.test.ts b/packages/shared/src/workspace-commands.test.ts index 1fc74478cd..9a5c610b68 100644 --- a/packages/shared/src/workspace-commands.test.ts +++ b/packages/shared/src/workspace-commands.test.ts @@ -53,4 +53,26 @@ describe("workspace command helpers", () => { expect(match).toEqual(expect.objectContaining({ id: "runtime-web" })); }); + + it("does not match a stale runtime service after the configured command changes", () => { + const workspaceRuntime = { + commands: [ + { id: "web", name: "web", kind: "service", command: "pnpm dev:once --tailscale-auth", cwd: "." }, + ], + }; + const command = findWorkspaceCommandDefinition(workspaceRuntime, "web"); + expect(command).not.toBeNull(); + + const match = matchWorkspaceRuntimeServiceToCommand(command!, [ + { + id: "runtime-web", + serviceName: "web", + command: "pnpm dev", + cwd: "/repo", + configIndex: null, + }, + ]); + + expect(match).toBeNull(); + }); }); diff --git a/packages/shared/src/workspace-commands.ts b/packages/shared/src/workspace-commands.ts index 0510d3fbc6..dd98ef3dc0 100644 --- a/packages/shared/src/workspace-commands.ts +++ b/packages/shared/src/workspace-commands.ts @@ -166,6 +166,10 @@ export function scoreWorkspaceRuntimeServiceMatch( command: Pick, runtimeService: Pick, ) { + if (command.command && runtimeService.command && runtimeService.command !== command.command) { + return -1; + } + if (command.serviceIndex !== null && runtimeService.configIndex !== null && runtimeService.configIndex !== undefined) { return runtimeService.configIndex === command.serviceIndex ? 100 : -1; } diff --git a/ui/src/App.tsx b/ui/src/App.tsx index ddf6d1bb46..1a82d1a613 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -10,6 +10,7 @@ import { AgentDetail } from "./pages/AgentDetail"; import { Projects } from "./pages/Projects"; import { ProjectDetail } from "./pages/ProjectDetail"; import { ProjectWorkspaceDetail } from "./pages/ProjectWorkspaceDetail"; +import { Workspaces } from "./pages/Workspaces"; import { Issues } from "./pages/Issues"; import { IssueDetail } from "./pages/IssueDetail"; import { Routines } from "./pages/Routines"; @@ -90,6 +91,7 @@ function boardRoutes() { } /> } /> } /> + } /> } /> } /> } /> @@ -296,6 +298,7 @@ export function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/ui/src/components/CopyText.tsx b/ui/src/components/CopyText.tsx index fc399fa044..ff4134f34a 100644 --- a/ui/src/components/CopyText.tsx +++ b/ui/src/components/CopyText.tsx @@ -1,21 +1,34 @@ -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { cn } from "@/lib/utils"; interface CopyTextProps { text: string; /** What to display. Defaults to `text`. */ children?: React.ReactNode; + containerClassName?: string; className?: string; + ariaLabel?: string; + title?: string; /** Tooltip message shown after copying. Default: "Copied!" */ copiedLabel?: string; } -export function CopyText({ text, children, className, copiedLabel = "Copied!" }: CopyTextProps) { +export function CopyText({ + text, + children, + containerClassName, + className, + ariaLabel, + title, + copiedLabel = "Copied!", +}: CopyTextProps) { const [visible, setVisible] = useState(false); const [label, setLabel] = useState(copiedLabel); const timerRef = useRef>(undefined); const triggerRef = useRef(null); + useEffect(() => () => clearTimeout(timerRef.current), []); + const handleClick = useCallback(async () => { try { if (navigator.clipboard && window.isSecureContext) { @@ -45,10 +58,12 @@ export function CopyText({ text, children, className, copiedLabel = "Copied!" }: }, [copiedLabel, text]); return ( - + - + + + Workspace settings + + Edit the concrete path, repo, branch, provisioning, teardown, and runtime overrides attached to this execution workspace. + + + + + - + -
- - setForm((current) => current ? { ...current, name: event.target.value } : current)} - placeholder="Execution workspace name" - /> - - - - setForm((current) => current ? { ...current, branchName: event.target.value } : current)} - placeholder="PAP-946-workspace" - /> - - - - setForm((current) => current ? { ...current, cwd: event.target.value } : current)} - placeholder="/absolute/path/to/workspace" - /> - - - - setForm((current) => current ? { ...current, providerRef: event.target.value } : current)} - placeholder="/path/to/worktree or provider ref" - /> - - - - setForm((current) => current ? { ...current, repoUrl: event.target.value } : current)} - placeholder="https://github.com/org/repo" - /> - - - - setForm((current) => current ? { ...current, baseRef: event.target.value } : current)} - placeholder="origin/main" - /> - - - -