[codex] improve issue and routine UI responsiveness (#3744)

## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies
> - Operators rely on issue, inbox, and routine views to understand what
the company is doing in real time
> - Those views need to stay fast and readable even when issue lists,
markdown comments, and run metadata get large
> - The current branch had a coherent set of UI and live-update
improvements spread across issue search, issue detail rendering, routine
affordances, and workspace lookups
> - This pull request groups those board-facing changes into one
standalone branch that can merge independently of the heartbeat/runtime
work
> - The benefit is a faster, clearer issue and routine workflow without
changing the underlying task model

## What Changed

- Show routine execution issues by default and rename the filter to
`Hide routine runs` so the default state no longer looks like an active
filter.
- Show the routine name in the run dialog and tighten the issue
properties pane with a workspace link, copy-on-click behavior, and an
inline parent arrow.
- Reduce issue detail rerenders, keep queued issue chat mounted, improve
issues page search responsiveness, and speed up issues first paint.
- Add inbox "other search results", refresh visible issue runs after
status updates, and optimize workspace lookups through summary-mode
execution workspace queries.
- Improve markdown wrapping and scrolling behavior for long strings and
self-comment code blocks.
- Relax the markdown sanitizer assertion so the test still validates
safety after the new wrap-friendly inline styles.

## Verification

- `pnpm vitest run ui/src/components/IssuesList.test.tsx
ui/src/lib/inbox.test.ts ui/src/pages/Issues.test.tsx
ui/src/context/BreadcrumbContext.test.tsx
ui/src/context/LiveUpdatesProvider.test.ts
ui/src/components/MarkdownBody.test.tsx
ui/src/api/execution-workspaces.test.ts
server/src/__tests__/execution-workspaces-routes.test.ts`

## Risks

- This touches several issue-facing UI surfaces at once, so regressions
would most likely show up as stale rendering, search result mismatches,
or small markdown presentation differences.
- The workspace lookup optimization depends on the summary-mode route
shape staying aligned between server and UI.

## Model Used

- OpenAI Codex, GPT-5-based coding agent in the Codex CLI environment.
Exact backend model deployment ID was not exposed in-session.
Tool-assisted editing and shell execution were used.

## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] 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 <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-04-15 15:54:05 -05:00 committed by GitHub
parent 7463479fc8
commit d4c3899ca4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 1035 additions and 241 deletions

View file

@ -20,7 +20,7 @@ import { formatDate, cn, projectUrl } from "../lib/utils";
import { timeAgo } from "../lib/timeAgo";
import { Separator } from "@/components/ui/separator";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { User, Hexagon, ArrowUpRight, Tag, Plus, GitBranch, FolderOpen, Copy, Check } from "lucide-react";
import { User, Hexagon, ArrowUpRight, Tag, Plus, GitBranch, FolderOpen, Check, ExternalLink } from "lucide-react";
import { AgentIcon } from "./AgentIconPicker";
function TruncatedCopyable({ value, icon: Icon }: { value: string; icon: React.ComponentType<{ className?: string }> }) {
@ -39,17 +39,15 @@ function TruncatedCopyable({ value, icon: Icon }: { value: string; icon: React.C
return (
<div className="flex items-start gap-1.5 min-w-0 flex-1">
<Icon className="h-3.5 w-3.5 text-muted-foreground shrink-0 mt-0.5" />
<span className="text-sm font-mono min-w-0 break-all">
{value}
</span>
<button
type="button"
className="shrink-0 p-0.5 rounded hover:bg-accent/50 transition-colors text-muted-foreground hover:text-foreground"
className="text-sm font-mono min-w-0 break-all text-left cursor-pointer hover:text-foreground transition-colors"
onClick={handleCopy}
title={copied ? "Copied!" : "Copy"}
title={copied ? "Copied!" : "Click to copy"}
>
{copied ? <Check className="h-3 w-3 text-green-500" /> : <Copy className="h-3 w-3" />}
{value}
</button>
{copied && <Check className="h-3 w-3 text-green-500 shrink-0 mt-0.5" />}
</div>
);
}
@ -704,16 +702,25 @@ export function IssueProperties({
if (!issue.parentId) return null;
return allIssues?.find((candidate) => candidate.id === issue.parentId) ?? null;
}, [allIssues, issue.parentId]);
const parentIdentifier = issue.ancestors?.[0]?.identifier ?? currentParentIssue?.identifier;
const parentTitle = issue.ancestors?.[0]?.title ?? currentParentIssue?.title ?? issue.parentId?.slice(0, 8);
const parentTrigger = issue.parentId ? (
<span className="text-sm break-words min-w-0">
{issue.ancestors?.[0]?.identifier ?? currentParentIssue?.identifier
? `${issue.ancestors?.[0]?.identifier ?? currentParentIssue?.identifier} `
: ""}
{issue.ancestors?.[0]?.title ?? currentParentIssue?.title ?? issue.parentId.slice(0, 8)}
<span className="text-sm break-words min-w-0 inline">
{parentIdentifier ? `${parentIdentifier} ` : ""}
{parentTitle}
</span>
) : (
<span className="text-sm text-muted-foreground">No parent</span>
);
const parentLink = issue.parentId ? (
<Link
to={`/issues/${parentIdentifier ?? issue.parentId}`}
className="inline-flex items-center justify-center h-5 w-5 rounded hover:bg-accent/50 transition-colors text-muted-foreground hover:text-foreground"
onClick={(e) => e.stopPropagation()}
>
<ArrowUpRight className="h-3 w-3" />
</Link>
) : undefined;
const parentOptions = (allIssues ?? [])
.filter((candidate) => candidate.id !== issue.id)
.filter((candidate) => !descendantIssueIds.has(candidate.id))
@ -939,15 +946,7 @@ export function IssueProperties({
triggerContent={parentTrigger}
triggerClassName="min-w-0 max-w-full"
popoverClassName="w-72"
extra={issue.parentId ? (
<Link
to={`/issues/${issue.ancestors?.[0]?.identifier ?? currentParentIssue?.identifier ?? issue.parentId}`}
className="inline-flex items-center justify-center h-5 w-5 rounded hover:bg-accent/50 transition-colors text-muted-foreground hover:text-foreground"
onClick={(e) => e.stopPropagation()}
>
<ArrowUpRight className="h-3 w-3" />
</Link>
) : undefined}
extra={parentLink}
>
{parentContent}
</PropertyPicker>
@ -1060,10 +1059,21 @@ export function IssueProperties({
)}
</div>
{issue.currentExecutionWorkspace?.branchName || issue.currentExecutionWorkspace?.cwd ? (
{issue.currentExecutionWorkspace?.branchName || issue.currentExecutionWorkspace?.cwd || issue.executionWorkspaceId ? (
<>
<Separator />
<div className="space-y-1">
{issue.executionWorkspaceId && (
<PropertyRow label="Workspace">
<Link
to={`/execution-workspaces/${issue.executionWorkspaceId}`}
className="text-sm text-primary hover:underline inline-flex items-center gap-1"
>
View workspace
<ExternalLink className="h-3 w-3" />
</Link>
</PropertyRow>
)}
{issue.currentExecutionWorkspace?.branchName && (
<PropertyRow label="Branch">
<TruncatedCopyable