From fee514efcb60d00775407dc4fbacf247fbe0d7c1 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 20 Apr 2026 06:14:32 -0500 Subject: [PATCH] [codex] Improve workspace navigation and runtime UI (#4089) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip agents do real work in project and execution workspaces. > - Operators need workspace state to be visible, navigable, and copyable without digging through raw run logs. > - The branch included related workspace cards, navigation, runtime controls, stale-service handling, and issue-property visibility. > - These changes share the workspace UI and runtime-control surfaces and can stand alone from unrelated access/profile work. > - This pull request groups the workspace experience changes into one standalone branch. > - The benefit is a clearer workspace overview, better metadata copy flows, and more accurate runtime service controls. ## What Changed - Polished project workspace summary cards and made workspace metadata copyable. - Added a workspace navigation overview and extracted reusable project workspace content. - Squared and polished the execution workspace configuration page. - Fixed stale workspace command matching and hid stopped stale services in runtime controls. - Showed live workspace service context in issue properties. ## Verification - `pnpm install --frozen-lockfile` - `pnpm exec vitest run ui/src/components/ProjectWorkspaceSummaryCard.test.tsx ui/src/lib/project-workspaces-tab.test.ts ui/src/components/Sidebar.test.tsx ui/src/components/WorkspaceRuntimeControls.test.tsx ui/src/components/IssueProperties.test.tsx` - `pnpm exec vitest run packages/shared/src/workspace-commands.test.ts --config /dev/null` because the root Vitest project config does not currently include `packages/shared` tests. - Split integration check: merged after runtime/governance, dev-infra/backups, and access/profiles with no merge conflicts. - Confirmed this branch does not include `pnpm-lock.yaml`. ## Risks - Medium risk: touches workspace navigation, runtime controls, and issue property rendering. - Visual layout changes may need browser QA, especially around smaller screens and dense workspace metadata. - No database migrations are included. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5.4 tool-enabled coding model, agentic code-editing/runtime with local shell and GitHub CLI access; exact context window and reasoning mode are not exposed by the Paperclip harness. ## 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 run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- .../shared/src/workspace-commands.test.ts | 22 + packages/shared/src/workspace-commands.ts | 4 + ui/src/App.tsx | 3 + ui/src/components/CopyText.tsx | 21 +- ui/src/components/IssueProperties.test.tsx | 187 +++++++- ui/src/components/IssueProperties.tsx | 51 +- .../ProjectWorkspaceSummaryCard.test.tsx | 101 +++- .../ProjectWorkspaceSummaryCard.tsx | 49 +- .../components/ProjectWorkspacesContent.tsx | 119 +++++ ui/src/components/Sidebar.test.tsx | 153 ++++++ ui/src/components/Sidebar.tsx | 10 + .../WorkspaceRuntimeControls.test.tsx | 104 ++++ .../components/WorkspaceRuntimeControls.tsx | 26 +- ui/src/lib/company-routes.ts | 1 + ui/src/lib/project-workspaces-tab.test.ts | 94 +++- ui/src/lib/project-workspaces-tab.ts | 36 +- ui/src/pages/ExecutionWorkspaceDetail.tsx | 445 ++++++++++-------- ui/src/pages/ProjectDetail.tsx | 110 +---- ui/src/pages/Workspaces.tsx | 163 +++++++ 19 files changed, 1348 insertions(+), 351 deletions(-) create mode 100644 ui/src/components/ProjectWorkspacesContent.tsx create mode 100644 ui/src/components/Sidebar.test.tsx create mode 100644 ui/src/pages/Workspaces.tsx diff --git a/packages/shared/src/workspace-commands.test.ts b/packages/shared/src/workspace-commands.test.ts index 1fc74478..9a5c610b 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 0510d3fb..dd98ef3d 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 ddf6d1bb..1a82d1a6 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 fc399fa0..ff4134f3 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" - /> - - - -