PAP-10430: split Issue-to-Task copy migration (#7651)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The board UI is the operator surface where users create, assign, monitor, and review work items. > - The product language is moving toward "tasks" for user-facing work items while the internal API and database still use "issues". > - PR #7543 bundled this copy migration with broader information-architecture work, which made the branch too large for Greptile review. > - This pull request peels the Issue-to-Task copy migration into a smaller, independently reviewable change. > - The benefit is clearer user-facing terminology, less agent confusion via the Paperclip skill note, and a smaller PR that Greptile can review. ## Linked Issues or Issue Description Refs #7645 Refs #7543 Refs PAP-10430 This PR was split out of #7543 so the Issue-to-Task copy migration can be reviewed separately and the remaining IA PR can fall under Greptile's file limit. ## What Changed - Preserves Scott Tong's original `PAP-57` copy-only commit, with author and co-author credit intact, to rename user-facing "Issues" copy to "Tasks" across the UI while keeping routes/API/internal symbols as `issue`. - Updates onboarding and release-smoke browser selectors from `Create & Open Issue` to `Create & Open Task`. - Adds a terminology note to `skills/paperclip/SKILL.md` clarifying that task and issue refer to the same Paperclip work item. - Resolves the only cherry-pick conflict by keeping current search artifacts support and changing visible search copy to "tasks". ## Verification - `pnpm --filter @paperclipai/ui build` passed. - `NODE_ENV=test pnpm exec vitest run ui/src/components/IssuesList.test.tsx ui/src/components/Sidebar.test.tsx ui/src/components/NewIssueDialog.test.tsx ui/src/pages/IssueDetail.test.tsx` passed: 4 files, 66 tests. - `git diff --check origin/master...HEAD` passed. - Diff is 80 files, below Greptile's 100-file limit. - Before/after UI copy examples: "Issues" -> "Tasks", "New Issue" -> "New Task", "Create & Open Issue" -> "Create & Open Task". ## Risks - Medium copy-risk: this intentionally changes user-facing terminology broadly while keeping internal issue identifiers and routes unchanged. - Some docs and APIs still say `issue`; the skill note clarifies this so agents do not treat task and issue as separate entities. - Browser-level visual validation is expected from CI because this local container is missing usable browser dependencies. > 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 Scott Tong authored the original `PAP-57` copy migration, assisted by Claude Opus 4.8 and Paperclip agents per the preserved commit metadata. Codex / GPT-5-class coding agent with shell, GitHub CLI, and repository access performed the PR split, conflict resolution, skill note, and verification. ## 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 searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [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] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: scotttong <scott.tong@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -156,8 +156,8 @@ export function ApprovalDetail() {
|
||||
? {
|
||||
label:
|
||||
(linkedIssues?.length ?? 0) > 1
|
||||
? "Review linked issues"
|
||||
: "Review linked issue",
|
||||
? "Review linked tasks"
|
||||
: "Review linked task",
|
||||
to: `/issues/${primaryLinkedIssue.identifier ?? primaryLinkedIssue.id}`,
|
||||
}
|
||||
: linkedAgentId
|
||||
@@ -183,7 +183,7 @@ export function ApprovalDetail() {
|
||||
<div>
|
||||
<p className="text-sm text-green-800 dark:text-green-100 font-medium">Approval confirmed</p>
|
||||
<p className="text-xs text-green-700 dark:text-green-200/90">
|
||||
Requesting agent was notified to review this approval and linked issues.
|
||||
Requesting agent was notified to review this approval and linked tasks.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -240,7 +240,7 @@ export function ApprovalDetail() {
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
{linkedIssues && linkedIssues.length > 0 && (
|
||||
<div className="pt-2 border-t border-border/60">
|
||||
<p className="text-xs text-muted-foreground mb-1.5">Linked Issues</p>
|
||||
<p className="text-xs text-muted-foreground mb-1.5">Linked Tasks</p>
|
||||
<div className="space-y-1.5">
|
||||
{linkedIssues.map((issue) => (
|
||||
<Link
|
||||
@@ -256,7 +256,7 @@ export function ApprovalDetail() {
|
||||
))}
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground mt-2">
|
||||
Linked issues remain open until the requesting agent follows up and closes them.
|
||||
Linked tasks remain open until the requesting agent follows up and closes them.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -241,7 +241,7 @@ export function Companies() {
|
||||
<div className="flex items-center gap-1.5">
|
||||
<CircleDot className="h-3.5 w-3.5" />
|
||||
<span>
|
||||
{issueCount} {issueCount === 1 ? "issue" : "issues"}
|
||||
{issueCount} {issueCount === 1 ? "task" : "tasks"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 tabular-nums">
|
||||
|
||||
@@ -175,7 +175,7 @@ export function CompanyAccess() {
|
||||
title: "Member removed",
|
||||
body:
|
||||
result.reassignedIssueCount > 0
|
||||
? `${result.reassignedIssueCount} assigned issue${result.reassignedIssueCount === 1 ? "" : "s"} cleaned up.`
|
||||
? `${result.reassignedIssueCount} assigned task${result.reassignedIssueCount === 1 ? "" : "s"} cleaned up.`
|
||||
: undefined,
|
||||
tone: "success",
|
||||
});
|
||||
@@ -449,14 +449,14 @@ export function CompanyAccess() {
|
||||
<div className="text-sm text-muted-foreground">{removingMember.user?.email || removingMember.principalId}</div>
|
||||
<div className="mt-2 text-sm text-muted-foreground">
|
||||
{assignedIssuesQuery.isLoading
|
||||
? "Checking assigned issues..."
|
||||
: `${assignedIssues.length} open assigned issue${assignedIssues.length === 1 ? "" : "s"}`}
|
||||
? "Checking assigned tasks..."
|
||||
: `${assignedIssues.length} open assigned task${assignedIssues.length === 1 ? "" : "s"}`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{assignedIssues.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">Issue reassignment</div>
|
||||
<div className="text-sm font-medium">Task reassignment</div>
|
||||
<select
|
||||
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
|
||||
value={reassignmentTarget}
|
||||
@@ -491,7 +491,7 @@ export function CompanyAccess() {
|
||||
))}
|
||||
{assignedIssues.length > 6 ? (
|
||||
<div className="px-3 py-2 text-sm text-muted-foreground">
|
||||
{assignedIssues.length - 6} more issue{assignedIssues.length - 6 === 1 ? "" : "s"}
|
||||
{assignedIssues.length - 6} more task{assignedIssues.length - 6 === 1 ? "" : "s"}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -422,7 +422,7 @@ export function CompanyEnvironments() {
|
||||
<h1 className="text-lg font-semibold">Company Environments</h1>
|
||||
</div>
|
||||
<p className="max-w-3xl text-sm text-muted-foreground">
|
||||
Define reusable execution targets for projects, issue workspaces, and remote-capable adapters.
|
||||
Define reusable execution targets for projects, task workspaces, and remote-capable adapters.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1005,7 +1005,7 @@ export function CompanyExport() {
|
||||
onClick={() => setTaskLimit((prev) => prev + TASKS_PAGE_SIZE)}
|
||||
className="w-full rounded-md border border-border px-3 py-1.5 text-xs text-muted-foreground hover:bg-accent/30 hover:text-foreground transition-colors"
|
||||
>
|
||||
Show more issues ({visibleTaskChildren} of {totalTaskChildren})
|
||||
Show more tasks ({visibleTaskChildren} of {totalTaskChildren})
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -805,7 +805,7 @@ export function Costs() {
|
||||
<Card>
|
||||
<CardHeader className="px-5 pt-5 pb-2">
|
||||
<CardTitle className="text-base">By project</CardTitle>
|
||||
<CardDescription>Run costs attributed through project-linked issues.</CardDescription>
|
||||
<CardDescription>Run costs attributed through project-linked tasks.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 px-5 pb-5 pt-2">
|
||||
{(spendData?.byProject.length ?? 0) === 0 ? (
|
||||
|
||||
@@ -295,10 +295,10 @@ export function Dashboard() {
|
||||
<ChartCard title="Run Activity" subtitle="Last 14 days">
|
||||
<RunActivityChart activity={data.runActivity} />
|
||||
</ChartCard>
|
||||
<ChartCard title="Issues by Priority" subtitle="Last 14 days">
|
||||
<ChartCard title="Tasks by Priority" subtitle="Last 14 days">
|
||||
<PriorityChart issues={issues ?? []} />
|
||||
</ChartCard>
|
||||
<ChartCard title="Issues by Status" subtitle="Last 14 days">
|
||||
<ChartCard title="Tasks by Status" subtitle="Last 14 days">
|
||||
<IssueStatusChart issues={issues ?? []} />
|
||||
</ChartCard>
|
||||
<ChartCard title="Success Rate" subtitle="Last 14 days">
|
||||
|
||||
@@ -470,7 +470,7 @@ export function DesignGuide() {
|
||||
|
||||
<SubSection title="With icons">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Button><Plus /> New Issue</Button>
|
||||
<Button><Plus /> New Task</Button>
|
||||
<Button variant="outline"><Upload /> Upload</Button>
|
||||
<Button variant="destructive"><Trash2 /> Delete</Button>
|
||||
<Button size="sm"><Plus /> Add</Button>
|
||||
@@ -727,11 +727,11 @@ export function DesignGuide() {
|
||||
checked={menuChecked}
|
||||
onCheckedChange={(value) => setMenuChecked(value === true)}
|
||||
>
|
||||
Watch issue
|
||||
Watch task
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuItem variant="destructive">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Delete issue
|
||||
Delete task
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -784,7 +784,7 @@ export function DesignGuide() {
|
||||
</SheetTrigger>
|
||||
<SheetContent side="right">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Issue Properties</SheetTitle>
|
||||
<SheetTitle>Task Properties</SheetTitle>
|
||||
<SheetDescription>Edit metadata without leaving the current page.</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="space-y-4 px-4">
|
||||
@@ -836,7 +836,7 @@ export function DesignGuide() {
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<CircleDot className="h-4 w-4" />
|
||||
Issues
|
||||
Tasks
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
@@ -847,7 +847,7 @@ export function DesignGuide() {
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<Plus className="h-4 w-4" />
|
||||
Create new issue
|
||||
Create new task
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
@@ -870,7 +870,7 @@ export function DesignGuide() {
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Issue List</BreadcrumbPage>
|
||||
<BreadcrumbPage>Task List</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
@@ -899,7 +899,7 @@ export function DesignGuide() {
|
||||
<SubSection title="Metric Cards">
|
||||
<div className="grid md:grid-cols-2 xl:grid-cols-4 gap-4">
|
||||
<MetricCard icon={Bot} value={12} label="Active Agents" description="+3 this week" />
|
||||
<MetricCard icon={CircleDot} value={48} label="Open Issues" />
|
||||
<MetricCard icon={CircleDot} value={48} label="Open Tasks" />
|
||||
<MetricCard icon={DollarSign} value="$1,234" label="Monthly Cost" description="Under budget" />
|
||||
<MetricCard icon={Zap} value="99.9%" label="Uptime" />
|
||||
</div>
|
||||
@@ -1298,7 +1298,7 @@ export function DesignGuide() {
|
||||
</div>
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground hover:bg-accent/50 hover:text-accent-foreground cursor-pointer">
|
||||
<CircleDot className="h-4 w-4" />
|
||||
Issues
|
||||
Tasks
|
||||
<span className="ml-auto text-xs bg-primary text-primary-foreground rounded-full px-1.5 py-0.5">
|
||||
12
|
||||
</span>
|
||||
@@ -1331,7 +1331,7 @@ export function DesignGuide() {
|
||||
{/* ============================================================ */}
|
||||
{/* GROUPED LIST (Issues pattern) */}
|
||||
{/* ============================================================ */}
|
||||
<Section title="Grouped List (Issues pattern)">
|
||||
<Section title="Grouped List (Tasks pattern)">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-muted/50 rounded-t-md">
|
||||
<StatusIcon status="in_progress" />
|
||||
@@ -1588,7 +1588,7 @@ export function DesignGuide() {
|
||||
<div className="border border-border rounded-md divide-y divide-border text-sm">
|
||||
{[
|
||||
["Cmd+K / Ctrl+K", "Open Command Palette"],
|
||||
["C", "New Issue (outside inputs)"],
|
||||
["C", "New Task (outside inputs)"],
|
||||
["[", "Toggle Sidebar"],
|
||||
["]", "Toggle Properties Panel"],
|
||||
|
||||
@@ -1604,7 +1604,7 @@ export function DesignGuide() {
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Issue Output Surface">
|
||||
<Section title="Task Output Surface">
|
||||
<SubSection title="Multiple outputs (primary video + 'Also produced')">
|
||||
<IssueOutputSection workProducts={DESIGN_GUIDE_OUTPUTS} />
|
||||
</SubSection>
|
||||
@@ -1613,7 +1613,7 @@ export function DesignGuide() {
|
||||
</SubSection>
|
||||
<SubSection title="Empty state">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
When an issue has produced no artifact work products, the Output section renders nothing
|
||||
When a task has produced no artifact work products, the Output section renders nothing
|
||||
at all (no placeholder card).
|
||||
</p>
|
||||
</SubSection>
|
||||
|
||||
@@ -293,7 +293,7 @@ describe("ExecutionWorkspaceDetail plugin slots", () => {
|
||||
await render();
|
||||
|
||||
expect(container.textContent).toContain("Workspace plugin tab is not available.");
|
||||
expect(container.querySelector('a[href="/execution-workspaces/workspace-1/issues"]')?.textContent).toBe("Back to issues");
|
||||
expect(container.querySelector('a[href="/execution-workspaces/workspace-1/issues"]')?.textContent).toBe("Back to tasks");
|
||||
expect(container.textContent).not.toContain("Workspace routines");
|
||||
expect(container.querySelector('[data-testid="plugin-slot-mount"]')).toBeNull();
|
||||
});
|
||||
@@ -309,7 +309,7 @@ describe("ExecutionWorkspaceDetail plugin slots", () => {
|
||||
|
||||
const tabLabels = Array.from(container.querySelectorAll("[data-tab-value]")).map((tab) => tab.textContent);
|
||||
expect(tabLabels).toEqual([
|
||||
"Issues",
|
||||
"Tasks",
|
||||
"Services",
|
||||
"Changes",
|
||||
"Configuration",
|
||||
|
||||
@@ -67,7 +67,7 @@ type OrderedExecutionWorkspaceTabItem = {
|
||||
|
||||
const DEFAULT_PLUGIN_DETAIL_TAB_ORDER = 100;
|
||||
const EXECUTION_WORKSPACE_BASE_TAB_ITEMS: OrderedExecutionWorkspaceTabItem[] = [
|
||||
{ value: "issues", label: "Issues", order: 10 },
|
||||
{ value: "issues", label: "Tasks", order: 10 },
|
||||
{ value: "services", label: "Services", order: 20 },
|
||||
{ value: "configuration", label: "Configuration", order: 30 },
|
||||
{ value: "runtime_logs", label: "Runtime logs", order: 40 },
|
||||
@@ -1077,7 +1077,7 @@ export function ExecutionWorkspaceDetail() {
|
||||
"None"
|
||||
)}
|
||||
</DetailRow>
|
||||
<DetailRow label="Source issue">
|
||||
<DetailRow label="Source task">
|
||||
{sourceIssue ? (
|
||||
<Link to={issueUrl(sourceIssue)} className="hover:underline">
|
||||
{sourceIssue.identifier ?? sourceIssue.id} · {sourceIssue.title}
|
||||
@@ -1218,7 +1218,7 @@ export function ExecutionWorkspaceDetail() {
|
||||
) : isExecutionWorkspacePluginTab(activeTab) ? (
|
||||
<MissingPluginTabPlaceholder
|
||||
defaultTabHref={executionWorkspaceTabPath(workspace.id, "issues")}
|
||||
defaultTabLabel="Back to issues"
|
||||
defaultTabLabel="Back to tasks"
|
||||
/>
|
||||
) : activeTab === "routines" ? (
|
||||
<ExecutionWorkspaceRoutinesList
|
||||
|
||||
@@ -1495,7 +1495,7 @@ export function Inbox() {
|
||||
return { previousData };
|
||||
},
|
||||
onError: (err, id, context) => {
|
||||
setActionError(err instanceof Error ? err.message : "Failed to archive issue");
|
||||
setActionError(err instanceof Error ? err.message : "Failed to archive task");
|
||||
setArchivingIssueIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(id);
|
||||
@@ -2208,7 +2208,7 @@ export function Inbox() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="everything">All categories</SelectItem>
|
||||
<SelectItem value="issues_i_touched">My recent issues</SelectItem>
|
||||
<SelectItem value="issues_i_touched">My recent tasks</SelectItem>
|
||||
<SelectItem value="join_requests">Join requests</SelectItem>
|
||||
<SelectItem value="approvals">Approvals</SelectItem>
|
||||
<SelectItem value="failed_runs">Failed runs</SelectItem>
|
||||
@@ -2454,8 +2454,8 @@ export function Inbox() {
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="-mr-2 text-muted-foreground"
|
||||
title={`New issue in ${group.label}`}
|
||||
aria-label={`New issue in ${group.label}`}
|
||||
title={`New task in ${group.label}`}
|
||||
aria-label={`New task in ${group.label}`}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
openCreateIssueForGroup(group);
|
||||
|
||||
@@ -291,7 +291,7 @@ export function InstanceExperimentalSettings() {
|
||||
<h2 className="text-sm font-semibold">Enable Isolated Workspaces</h2>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
Show execution workspace controls in project configuration and allow isolated workspace behavior for new
|
||||
and existing issue runs.
|
||||
and existing task runs.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
@@ -328,9 +328,9 @@ export function InstanceExperimentalSettings() {
|
||||
<section className="rounded-xl border border-border bg-card p-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<h2 className="text-sm font-semibold">Issue Plan Decomposition Panel</h2>
|
||||
<h2 className="text-sm font-semibold">Task Plan Decomposition Panel</h2>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
Show accepted-plan decomposition history on issue detail pages. Intended for debugging and validating
|
||||
Show accepted-plan decomposition history on task detail pages. Intended for debugging and validating
|
||||
subtask creation behavior while the presentation is still being refined.
|
||||
</p>
|
||||
</div>
|
||||
@@ -342,7 +342,7 @@ export function InstanceExperimentalSettings() {
|
||||
})
|
||||
}
|
||||
disabled={toggleMutation.isPending}
|
||||
aria-label="Toggle issue plan decomposition panel experimental setting"
|
||||
aria-label="Toggle task plan decomposition panel experimental setting"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
@@ -387,9 +387,9 @@ export function InstanceExperimentalSettings() {
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<h2 className="text-sm font-semibold">Auto-Create Issue Recovery Tasks</h2>
|
||||
<h2 className="text-sm font-semibold">Auto-Create Recovery Tasks</h2>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
Let the heartbeat scheduler create recovery issues for issue dependency chains found inside the
|
||||
Let the heartbeat scheduler create recovery tasks for task dependency chains found inside the
|
||||
configured lookback window.
|
||||
</p>
|
||||
</div>
|
||||
@@ -403,7 +403,7 @@ export function InstanceExperimentalSettings() {
|
||||
previewForEnable();
|
||||
}}
|
||||
disabled={recoveryActionPending}
|
||||
aria-label="Toggle issue graph liveness auto-recovery"
|
||||
aria-label="Toggle task graph liveness auto-recovery"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ export function InstanceGeneralSettings() {
|
||||
<div className="space-y-1.5">
|
||||
<h2 className="text-sm font-semibold">Keyboard shortcuts</h2>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
Enable app keyboard shortcuts, including inbox navigation and global shortcuts like creating issues or
|
||||
Enable app keyboard shortcuts, including inbox navigation and global shortcuts like creating tasks or
|
||||
toggling panels. This is off by default.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1028,7 +1028,7 @@ describe("IssueDetail", () => {
|
||||
|
||||
expect(container.textContent).toContain("Plan decomposition");
|
||||
expect(container.textContent).toContain("Plan revision 2");
|
||||
expect(container.textContent).toContain("2 of 2 child issues created");
|
||||
expect(container.textContent).toContain("2 of 2 child tasks created");
|
||||
expect(container.textContent).toContain("First child issue");
|
||||
expect(mockIssuesApi.listAcceptedPlanDecompositions).toHaveBeenCalledWith("issue-1");
|
||||
});
|
||||
@@ -1081,8 +1081,8 @@ describe("IssueDetail", () => {
|
||||
parentId: "parent-1",
|
||||
includeBlockedBy: true,
|
||||
});
|
||||
expect(container.querySelector('a[aria-label="Previous sub-issue: PAP-1 - Previous sibling"]')).toBeTruthy();
|
||||
expect(container.querySelector('a[aria-label="Next sub-issue: PAP-3 - Next sibling"]')).toBeTruthy();
|
||||
expect(container.querySelector('a[aria-label="Previous sub-task: PAP-1 - Previous sibling"]')).toBeTruthy();
|
||||
expect(container.querySelector('a[aria-label="Next sub-task: PAP-3 - Next sibling"]')).toBeTruthy();
|
||||
expect(container.textContent).toContain("Previous");
|
||||
expect(container.textContent).toContain("Previous sibling");
|
||||
expect(container.textContent).toContain("Next");
|
||||
@@ -1137,7 +1137,7 @@ describe("IssueDetail", () => {
|
||||
descendantOf: "issue-parent",
|
||||
includeBlockedBy: true,
|
||||
});
|
||||
expect(container.querySelector('a[aria-label="Next sub-issue: PAP-11 - First child"]')).toBeTruthy();
|
||||
expect(container.querySelector('a[aria-label="Next sub-task: PAP-11 - First child"]')).toBeTruthy();
|
||||
expect(container.textContent).toContain("Next");
|
||||
expect(container.textContent).toContain("First child");
|
||||
expect(mockIssueChatThreadRender.mock.calls.at(-1)?.[0].footer).toBeTruthy();
|
||||
@@ -1314,7 +1314,7 @@ describe("IssueDetail", () => {
|
||||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
const moreButton = container.querySelector('button[aria-label="More issue actions"]') as HTMLButtonElement | null;
|
||||
const moreButton = container.querySelector('button[aria-label="More task actions"]') as HTMLButtonElement | null;
|
||||
expect(moreButton).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
@@ -1424,7 +1424,7 @@ describe("IssueDetail", () => {
|
||||
metadata: { source: "issue_active_run_control", runId: "run-active-1" },
|
||||
});
|
||||
|
||||
const moreButton = container.querySelector('button[aria-label="More issue actions"]') as HTMLButtonElement | null;
|
||||
const moreButton = container.querySelector('button[aria-label="More task actions"]') as HTMLButtonElement | null;
|
||||
expect(moreButton).toBeTruthy();
|
||||
await act(async () => {
|
||||
moreButton!.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
|
||||
@@ -1690,7 +1690,7 @@ describe("IssueDetail", () => {
|
||||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
const moreButton = container.querySelector('button[aria-label="More issue actions"]') as HTMLButtonElement | null;
|
||||
const moreButton = container.querySelector('button[aria-label="More task actions"]') as HTMLButtonElement | null;
|
||||
expect(moreButton).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
@@ -1712,11 +1712,11 @@ describe("IssueDetail", () => {
|
||||
mode: "restore",
|
||||
releasePolicy: { strategy: "manual" },
|
||||
});
|
||||
expect(container.textContent).toContain("Restore issues cancelled by this subtree operation so work can resume.");
|
||||
expect(container.textContent).toContain("Restore tasks cancelled by this subtree operation so work can resume.");
|
||||
expect(container.textContent).toContain("Cancelled child");
|
||||
|
||||
const restoreApplyButton = Array.from(container.querySelectorAll("button"))
|
||||
.find((button) => button.textContent?.trim() === "Restore 1 issues");
|
||||
.find((button) => button.textContent?.trim() === "Restore 1 tasks");
|
||||
expect(restoreApplyButton).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
@@ -1792,7 +1792,7 @@ describe("IssueDetail", () => {
|
||||
expect(bodyScrollRegion?.className).toContain("overscroll-contain");
|
||||
|
||||
const cancelApplyButton = Array.from(dialogContent!.querySelectorAll("button"))
|
||||
.find((button) => button.textContent?.trim() === "Cancel 24 issues") as HTMLButtonElement | undefined;
|
||||
.find((button) => button.textContent?.trim() === "Cancel 24 tasks") as HTMLButtonElement | undefined;
|
||||
expect(cancelApplyButton).toBeTruthy();
|
||||
expect(cancelApplyButton!.disabled).toBe(true);
|
||||
|
||||
|
||||
@@ -192,14 +192,14 @@ const LEAF_WORK_CONTROL_MODE_LABEL: Partial<Record<IssueTreeControlMode, string>
|
||||
resume: "Resume work",
|
||||
};
|
||||
const TREE_CONTROL_MODE_HELP_TEXT: Record<IssueTreeControlMode, string> = {
|
||||
pause: "Pause active execution in this issue subtree until an explicit resume.",
|
||||
pause: "Pause active execution in this task subtree until an explicit resume.",
|
||||
resume: "Release the active subtree pause hold so held work can continue.",
|
||||
cancel: "Cancel non-terminal issues in this subtree and stop queued/running work where possible.",
|
||||
restore: "Restore issues cancelled by this subtree operation so work can resume.",
|
||||
cancel: "Cancel non-terminal tasks in this subtree and stop queued/running work where possible.",
|
||||
restore: "Restore tasks cancelled by this subtree operation so work can resume.",
|
||||
};
|
||||
const LEAF_WORK_CONTROL_MODE_HELP_TEXT: Partial<Record<IssueTreeControlMode, string>> = {
|
||||
pause: "Pause active execution on this issue until an explicit resume.",
|
||||
resume: "Release the active pause hold so this issue can continue.",
|
||||
pause: "Pause active execution on this task until an explicit resume.",
|
||||
resume: "Release the active pause hold so this task can continue.",
|
||||
};
|
||||
function issueTreeControlLabel(mode: IssueTreeControlMode, scope: "leaf" | "subtree") {
|
||||
return scope === "leaf"
|
||||
@@ -217,7 +217,7 @@ function treeControlPreviewErrorCopy(error: unknown): string {
|
||||
if (error instanceof ApiError) {
|
||||
if (error.status === 403) return "Only board users can preview subtree controls.";
|
||||
if (error.status === 409) return "Preview is stale because subtree hold state changed. Retry to refresh.";
|
||||
if (error.status === 422) return "This subtree action is currently invalid for the selected issues.";
|
||||
if (error.status === 422) return "This subtree action is currently invalid for the selected tasks.";
|
||||
}
|
||||
return error instanceof Error ? error.message : "Unable to load preview.";
|
||||
}
|
||||
@@ -606,7 +606,7 @@ function InboxMobileToolbar({
|
||||
onClick={() => { onHide(); setMenuOpen(false); }}
|
||||
>
|
||||
<EyeOff className="h-3 w-3" />
|
||||
Hide this issue
|
||||
Hide this task
|
||||
</button>
|
||||
)}
|
||||
</PopoverContent>
|
||||
@@ -1122,7 +1122,7 @@ function IssueDetailActivityTab({
|
||||
) : (
|
||||
<div className="space-y-1 text-xs text-muted-foreground tabular-nums">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<span className="font-medium text-foreground">This issue</span>
|
||||
<span className="font-medium text-foreground">This task</span>
|
||||
{issueCostSummary.hasCost ? (
|
||||
<span className="font-medium text-foreground">
|
||||
${issueCostSummary.cost.toFixed(4)}
|
||||
@@ -1149,7 +1149,7 @@ function IssueDetailActivityTab({
|
||||
{hasIssueTreeCost && issueTreeCostSummary ? (
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<span className="font-medium text-foreground">
|
||||
Including sub-issues {(issueTreeCostSummary.costCents / 100).toLocaleString(undefined, {
|
||||
Including sub-tasks {(issueTreeCostSummary.costCents / 100).toLocaleString(undefined, {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
minimumFractionDigits: 4,
|
||||
@@ -1168,7 +1168,7 @@ function IssueDetailActivityTab({
|
||||
{` (${issueTreeCostSummary.runCount} run${issueTreeCostSummary.runCount === 1 ? "" : "s"})`}
|
||||
</span>
|
||||
) : null}
|
||||
<span>{issueTreeCostSummary.issueCount} issue{issueTreeCostSummary.issueCount === 1 ? "" : "s"}</span>
|
||||
<span>{issueTreeCostSummary.issueCount} task{issueTreeCostSummary.issueCount === 1 ? "" : "s"}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1382,7 +1382,7 @@ export function IssueDetail() {
|
||||
}
|
||||
}, [hasLiveRuns, locallyQueuedCommentRunIds.size]);
|
||||
const sourceBreadcrumb = useMemo(
|
||||
() => readIssueDetailBreadcrumb(issueId, location.state, location.search) ?? { label: "Issues", href: "/issues" },
|
||||
() => readIssueDetailBreadcrumb(issueId, location.state, location.search) ?? { label: "Tasks", href: "/issues" },
|
||||
[issueId, location.state, location.search],
|
||||
);
|
||||
|
||||
@@ -1633,7 +1633,7 @@ export function IssueDetail() {
|
||||
() => mergeIssueComments(comments ?? [], optimisticComments),
|
||||
[comments, optimisticComments],
|
||||
);
|
||||
const breadcrumbTitle = issue?.title ?? issueId ?? "Issue";
|
||||
const breadcrumbTitle = issue?.title ?? issueId ?? "Task";
|
||||
const issueCacheRefs = useMemo(() => {
|
||||
const refs = new Set<string>();
|
||||
if (issueId) refs.add(issueId);
|
||||
@@ -1808,8 +1808,8 @@ export function IssueDetail() {
|
||||
queryClient.setQueryData(queryKeys.issues.list(context.selectedCompanyId), context.previousList);
|
||||
}
|
||||
pushToast({
|
||||
title: "Issue update failed",
|
||||
body: err instanceof Error ? err.message : "Unable to save issue changes",
|
||||
title: "Task update failed",
|
||||
body: err instanceof Error ? err.message : "Unable to save task changes",
|
||||
tone: "error",
|
||||
});
|
||||
},
|
||||
@@ -1886,7 +1886,7 @@ export function IssueDetail() {
|
||||
? treeControlScope === "leaf" ? "Work paused" : "Subtree paused"
|
||||
: `${modeLabel} applied`,
|
||||
body: result.kind === "release"
|
||||
? (result.hold.releaseReason?.trim() || (treeControlScope === "leaf" ? "Active issue pause released." : "Active subtree pause released."))
|
||||
? (result.hold.releaseReason?.trim() || (treeControlScope === "leaf" ? "Active task pause released." : "Active subtree pause released."))
|
||||
: result.hold.mode === "pause"
|
||||
? treeControlScope === "leaf"
|
||||
? `Work paused. ${cancelCount} run${cancelCount === 1 ? "" : "s"} cancelled.`
|
||||
@@ -1945,7 +1945,7 @@ export function IssueDetail() {
|
||||
title: "Work paused",
|
||||
body: cancelCount > 0
|
||||
? `Work paused. ${cancelCount} run${cancelCount === 1 ? "" : "s"} cancelled.`
|
||||
: "Work paused. This issue is held until resume.",
|
||||
: "Work paused. This task is held until resume.",
|
||||
tone: "success",
|
||||
});
|
||||
await Promise.all([
|
||||
@@ -1982,8 +1982,8 @@ export function IssueDetail() {
|
||||
},
|
||||
onError: (err) => {
|
||||
pushToast({
|
||||
title: "Issue update failed",
|
||||
body: err instanceof Error ? err.message : "Unable to save sub-issue changes",
|
||||
title: "Task update failed",
|
||||
body: err instanceof Error ? err.message : "Unable to save sub-task changes",
|
||||
tone: "error",
|
||||
});
|
||||
},
|
||||
@@ -2666,12 +2666,12 @@ export function IssueDetail() {
|
||||
onSuccess: () => {
|
||||
invalidateIssueCollections();
|
||||
navigate(sourceBreadcrumb.href.startsWith("/inbox") ? sourceBreadcrumb.href : "/inbox", { replace: true });
|
||||
pushToast({ title: "Issue archived from inbox", tone: "success" });
|
||||
pushToast({ title: "Task archived from inbox", tone: "success" });
|
||||
},
|
||||
onError: (err) => {
|
||||
pushToast({
|
||||
title: "Archive failed",
|
||||
body: err instanceof Error ? err.message : "Unable to archive this issue from the inbox",
|
||||
body: err instanceof Error ? err.message : "Unable to archive this task from the inbox",
|
||||
tone: "error",
|
||||
});
|
||||
},
|
||||
@@ -3261,9 +3261,9 @@ export function IssueDetail() {
|
||||
? "Pause work"
|
||||
: "Pause and stop work"
|
||||
: treeControlMode === "cancel"
|
||||
? `Cancel ${previewAffectedIssueCount} issues`
|
||||
? `Cancel ${previewAffectedIssueCount} tasks`
|
||||
: treeControlMode === "restore"
|
||||
? `Restore ${previewAffectedIssueCount} issues`
|
||||
? `Restore ${previewAffectedIssueCount} tasks`
|
||||
: treeControlScope === "leaf"
|
||||
? "Resume work"
|
||||
: "Resume subtree";
|
||||
@@ -3362,7 +3362,7 @@ export function IssueDetail() {
|
||||
{issue.hiddenAt && (
|
||||
<div className="flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||
<EyeOff className="h-4 w-4 shrink-0" />
|
||||
This issue is hidden
|
||||
This task is hidden
|
||||
</div>
|
||||
)}
|
||||
{activePauseHold && (
|
||||
@@ -3375,13 +3375,13 @@ export function IssueDetail() {
|
||||
</span>
|
||||
<span className="text-xs text-amber-900/80 dark:text-amber-100/80">
|
||||
{childIssues.length === 0
|
||||
? "Issue execution is held until resume. Human comments can still wake the assignee for triage."
|
||||
? "Task execution is held until resume. Human comments can still wake the assignee for triage."
|
||||
: "Root and descendant execution is held until resume. Human comments can still wake assignees for triage."}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-amber-900/80 dark:text-amber-100/80">
|
||||
{childIssues.length === 0
|
||||
? "1 issue held"
|
||||
? "1 task held"
|
||||
: `${heldDescendantCount} descendant${heldDescendantCount === 1 ? "" : "s"} held`}
|
||||
{activeRootPauseHold?.createdAt ? ` · started ${relativeTime(activeRootPauseHold.createdAt)}` : ""}
|
||||
</div>
|
||||
@@ -3427,7 +3427,7 @@ export function IssueDetail() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs">
|
||||
This issue is paused by ancestor{" "}
|
||||
This task is paused by ancestor{" "}
|
||||
{activePauseHoldRoot?.identifier ? (
|
||||
<Link to={createIssueDetailPath(activePauseHoldRoot.identifier)} className="underline">
|
||||
{activePauseHoldRoot.identifier}
|
||||
@@ -3435,7 +3435,7 @@ export function IssueDetail() {
|
||||
) : (
|
||||
activePauseHold.rootIssueId.slice(0, 8)
|
||||
)}
|
||||
. Resume from the root issue to deliver deferred work.
|
||||
. Resume from the root task to deliver deferred work.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -3491,7 +3491,7 @@ export function IssueDetail() {
|
||||
{issue.workMode === "planning" ? (
|
||||
<span
|
||||
className="inline-flex items-center rounded-full border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-[10px] font-medium text-amber-700 dark:text-amber-300 shrink-0"
|
||||
title="This issue is in planning mode."
|
||||
title="This task is in planning mode."
|
||||
>
|
||||
Planning
|
||||
</span>
|
||||
@@ -3550,7 +3550,7 @@ export function IssueDetail() {
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={copyIssueToClipboard}
|
||||
title="Copy issue as markdown"
|
||||
title="Copy task as markdown"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
@@ -3584,7 +3584,7 @@ export function IssueDetail() {
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={copyIssueToClipboard}
|
||||
title="Copy issue as markdown"
|
||||
title="Copy task as markdown"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
@@ -3607,8 +3607,8 @@ export function IssueDetail() {
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="shrink-0"
|
||||
aria-label="More issue actions"
|
||||
title="More issue actions"
|
||||
aria-label="More task actions"
|
||||
title="More task actions"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
@@ -3716,7 +3716,7 @@ export function IssueDetail() {
|
||||
}}
|
||||
>
|
||||
<EyeOff className="h-3 w-3" />
|
||||
Hide this Issue
|
||||
Hide this task
|
||||
</button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@@ -3793,7 +3793,7 @@ export function IssueDetail() {
|
||||
{showRichSubIssuesSection ? (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">Sub-issues</h3>
|
||||
<h3 className="text-sm font-medium text-muted-foreground">Sub-tasks</h3>
|
||||
</div>
|
||||
<IssuesList
|
||||
issues={childIssues}
|
||||
@@ -3809,7 +3809,7 @@ export function IssueDetail() {
|
||||
searchFilters={{ descendantOf: issue.id, includeBlockedBy: true }}
|
||||
searchWithinLoadedIssues
|
||||
baseCreateIssueDefaults={buildSubIssueDefaultsForViewer(issue, currentUserId)}
|
||||
createIssueLabel="Sub-issue"
|
||||
createIssueLabel="Sub-task"
|
||||
defaultSortField="workflow"
|
||||
showProgressSummary
|
||||
parentIssueIdForCostSummary={issue.id}
|
||||
@@ -3820,7 +3820,7 @@ export function IssueDetail() {
|
||||
<div className="flex flex-wrap items-center justify-end gap-2 min-w-0">
|
||||
<Button variant="outline" size="sm" onClick={openNewSubIssue} className="shrink-0 shadow-none">
|
||||
<Plus className="mr-1.5 h-3.5 w-3.5" />
|
||||
New Sub-issue
|
||||
New Sub-task
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -4062,7 +4062,7 @@ export function IssueDetail() {
|
||||
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto overscroll-contain px-6 py-4">
|
||||
{treeControlMode === "cancel" ? (
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/10 p-3 text-xs text-destructive">
|
||||
Cancelling a subtree is destructive. Non-terminal issues will be marked cancelled, and running or queued work will be interrupted where possible.
|
||||
Cancelling a subtree is destructive. Non-terminal tasks will be marked cancelled, and running or queued work will be interrupted where possible.
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -4120,7 +4120,7 @@ export function IssueDetail() {
|
||||
checked={treeControlCancelConfirmed}
|
||||
onChange={(event) => setTreeControlCancelConfirmed(event.target.checked)}
|
||||
/>
|
||||
<span>I understand this will cancel {previewAffectedIssueCount} issues.</span>
|
||||
<span>I understand this will cancel {previewAffectedIssueCount} tasks.</span>
|
||||
</label>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ export function Issues() {
|
||||
const issueLinkState = useMemo(
|
||||
() =>
|
||||
createIssueDetailLocationState(
|
||||
"Issues",
|
||||
"Tasks",
|
||||
`${location.pathname}${location.search}${location.hash}`,
|
||||
"issues",
|
||||
),
|
||||
@@ -116,7 +116,7 @@ export function Issues() {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([{ label: "Issues" }]);
|
||||
setBreadcrumbs([{ label: "Tasks" }]);
|
||||
}, [setBreadcrumbs]);
|
||||
|
||||
const issuePageSize = workspaceIdFilter ? WORKSPACE_FILTER_ISSUE_LIMIT : ISSUES_PAGE_SIZE;
|
||||
@@ -175,7 +175,7 @@ export function Issues() {
|
||||
});
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <EmptyState icon={CircleDot} message="Select a company to view issues." />;
|
||||
return <EmptyState icon={CircleDot} message="Select a company to view tasks." />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -17,7 +17,7 @@ export function MyIssues() {
|
||||
const { setBreadcrumbs } = useBreadcrumbs();
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([{ label: "My Issues" }]);
|
||||
setBreadcrumbs([{ label: "My Tasks" }]);
|
||||
}, [setBreadcrumbs]);
|
||||
|
||||
const { data: issues, isLoading, error } = useQuery({
|
||||
@@ -27,7 +27,7 @@ export function MyIssues() {
|
||||
});
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <EmptyState icon={ListTodo} message="Select a company to view your issues." />;
|
||||
return <EmptyState icon={ListTodo} message="Select a company to view your tasks." />;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
@@ -44,7 +44,7 @@ export function MyIssues() {
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
|
||||
{myIssues.length === 0 && (
|
||||
<EmptyState icon={ListTodo} message="No issues assigned to you." />
|
||||
<EmptyState icon={ListTodo} message="No tasks assigned to you." />
|
||||
)}
|
||||
|
||||
{myIssues.length > 0 && (
|
||||
|
||||
@@ -822,7 +822,7 @@ export function ProjectDetail() {
|
||||
<Tabs value={activeTab ?? "list"} onValueChange={(value) => handleTabChange(value as ProjectTab)}>
|
||||
<PageTabBar
|
||||
items={[
|
||||
{ value: "list", label: "Issues" },
|
||||
{ value: "list", label: "Tasks" },
|
||||
{ value: "overview", label: "Overview" },
|
||||
...(project.managedByPlugin ? [{ value: "plugin-operations", label: "Plugin operations" }] : []),
|
||||
...(showWorkspacesTab ? [{ value: "workspaces", label: "Workspaces" }] : []),
|
||||
|
||||
@@ -376,9 +376,9 @@ export function ProjectWorkspaceDetail() {
|
||||
request.action === "run"
|
||||
? "Workspace job completed."
|
||||
: request.action === "stop"
|
||||
? "Workspace service stopped. Issue execution is not paused."
|
||||
? "Workspace service stopped. Task execution is not paused."
|
||||
: request.action === "restart"
|
||||
? "Workspace service restarted. Issue execution is not paused."
|
||||
? "Workspace service restarted. Task execution is not paused."
|
||||
: "Workspace service started.",
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1264,7 +1264,7 @@ export function RoutineDetail() {
|
||||
|
||||
<TabsContent value="secrets" className="space-y-3">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Routine secrets apply to every issue this routine creates. They override matching keys in
|
||||
Routine secrets apply to every task this routine creates. They override matching keys in
|
||||
project and agent env. <span className="font-mono">PAPERCLIP_*</span> variables are reserved.
|
||||
</p>
|
||||
<EnvVarEditor
|
||||
|
||||
@@ -498,7 +498,7 @@ export function Routines() {
|
||||
Routines
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Recurring work definitions that materialize into auditable execution issues.
|
||||
Recurring work definitions that materialize into auditable execution tasks.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => setComposerOpen(true)}>
|
||||
|
||||
@@ -32,7 +32,7 @@ const IDENTIFIER_PATTERN = /^[A-Z]+-\d+$/;
|
||||
|
||||
const SCOPE_LABELS: Record<CompanySearchScope, string> = {
|
||||
all: "All",
|
||||
issues: "Issues",
|
||||
issues: "Tasks",
|
||||
comments: "Comments",
|
||||
documents: "Documents",
|
||||
artifacts: "Artifacts",
|
||||
@@ -45,7 +45,7 @@ type SubGroupKey = "issues" | "comments" | "documents" | "artifacts" | "agents"
|
||||
const SUBGROUP_ORDER: SubGroupKey[] = ["issues", "comments", "documents", "artifacts", "agents", "projects"];
|
||||
|
||||
const SUBGROUP_LABELS: Record<SubGroupKey, string> = {
|
||||
issues: "Issues",
|
||||
issues: "Tasks",
|
||||
comments: "Comments",
|
||||
documents: "Documents",
|
||||
artifacts: "Artifacts",
|
||||
@@ -346,7 +346,7 @@ export function Search() {
|
||||
}
|
||||
}
|
||||
}}
|
||||
placeholder="Search issues, comments, documents, agents, projects…"
|
||||
placeholder="Search tasks, comments, documents, artifacts, agents, projects…"
|
||||
aria-label="Search query"
|
||||
className="h-10 pl-9 pr-20 text-sm"
|
||||
/>
|
||||
@@ -456,7 +456,7 @@ function SearchTabContent({
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Type to search company memory.</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Issues, comments, plan documents, agents, projects — same surface, ranked by relevance.
|
||||
Tasks, comments, plan documents, artifacts, agents, projects — same surface, ranked by relevance.
|
||||
</p>
|
||||
</div>
|
||||
{recentSearches.length > 0 ? (
|
||||
@@ -483,7 +483,7 @@ function SearchTabContent({
|
||||
<ul className="space-y-1 text-xs text-muted-foreground">
|
||||
<li>
|
||||
<span className="font-medium text-foreground">Identifier lookup:</span> type{" "}
|
||||
<code className="rounded bg-muted px-1 py-0.5 text-[11px]">PAP-123</code> to jump straight to an issue.
|
||||
<code className="rounded bg-muted px-1 py-0.5 text-[11px]">PAP-123</code> to jump straight to a task.
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-medium text-foreground">Quoted phrases:</span> wrap a phrase in quotes to match the
|
||||
@@ -506,14 +506,14 @@ function SearchTabContent({
|
||||
<div className="text-base font-semibold">Couldn’t run that search</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{status ? `The server returned ${status}.` : "The request failed."} Your input and filters are still here, so
|
||||
you can retry or fall back to the Issues filter.
|
||||
you can retry or fall back to the Tasks filter.
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center justify-center gap-2">
|
||||
<Button onClick={refetch} variant="default" size="sm">
|
||||
Retry
|
||||
</Button>
|
||||
<Button onClick={navigateIssuesFallback} variant="outline" size="sm">
|
||||
Open Issues filter view
|
||||
Open Tasks filter view
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -561,10 +561,10 @@ function SearchTabContent({
|
||||
) : null}
|
||||
<Button onClick={openNewIssue} size="sm" variant="default">
|
||||
<Plus className="mr-1.5 h-4 w-4" />
|
||||
Create issue from this query
|
||||
Create task from this query
|
||||
</Button>
|
||||
<Button onClick={navigateIssuesFallback} size="sm" variant="ghost">
|
||||
Open Issues filter view
|
||||
Open Tasks filter view
|
||||
</Button>
|
||||
</div>
|
||||
<ul className="mt-2 space-y-0.5 text-xs text-muted-foreground">
|
||||
|
||||
@@ -1645,7 +1645,7 @@ function SecretsHowToUse() {
|
||||
</p>
|
||||
<p>
|
||||
Paperclip resolves the value server-side when the run starts and injects it as that env var. Project env
|
||||
applies to every issue in the project and overrides agent env on matching keys.
|
||||
applies to every task in the project and overrides agent env on matching keys.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -219,7 +219,7 @@ export function UserProfile() {
|
||||
(data?.topAgents ?? []).map((row) => ({
|
||||
key: row.agentId ?? "unknown",
|
||||
label: row.agentName ?? (row.agentId ? row.agentId.slice(0, 8) : "unknown"),
|
||||
sublabel: "Issue-linked usage",
|
||||
sublabel: "Task-linked usage",
|
||||
costCents: row.costCents,
|
||||
inputTokens: row.inputTokens,
|
||||
cachedInputTokens: row.cachedInputTokens,
|
||||
|
||||
Reference in New Issue
Block a user