diff --git a/server/src/__tests__/heartbeat-active-run-output-watchdog.test.ts b/server/src/__tests__/heartbeat-active-run-output-watchdog.test.ts index a5b3abb5..452bf6ee 100644 --- a/server/src/__tests__/heartbeat-active-run-output-watchdog.test.ts +++ b/server/src/__tests__/heartbeat-active-run-output-watchdog.test.ts @@ -73,6 +73,31 @@ if (!embeddedPostgresSupport.supported) { ); } +function errorHasPostgresCode(error: unknown, code: string): boolean { + let current: unknown = error; + for (let depth = 0; depth < 4; depth += 1) { + if (!current || typeof current !== "object") return false; + const record = current as { code?: unknown; cause?: unknown }; + if (record.code === code) return true; + current = record.cause; + } + return false; +} + +async function truncateCompaniesWithDeadlockRetry(db: ReturnType) { + for (let attempt = 0; attempt < 5; attempt += 1) { + try { + await db.execute(sql.raw(`TRUNCATE TABLE "companies" CASCADE`)); + return; + } catch (error) { + if (!errorHasPostgresCode(error, "40P01") || attempt === 4) { + throw error; + } + await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1))); + } + } +} + describeEmbeddedPostgres("active-run output watchdog", () => { let tempDb: Awaited> | null = null; let db: ReturnType; @@ -91,7 +116,7 @@ describeEmbeddedPostgres("active-run output watchdog", () => { if (activeRuns.length === 0) break; await new Promise((resolve) => setTimeout(resolve, 25)); } - await db.execute(sql.raw(`TRUNCATE TABLE "companies" CASCADE`)); + await truncateCompaniesWithDeadlockRetry(db); }); afterAll(async () => { diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 3898a981..4282dda3 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -13,6 +13,10 @@ description: > You run in **heartbeats** — short execution windows triggered by Paperclip. Each heartbeat, you wake up, check your work, do something useful, and exit. You do not run continuously. +## Terminology + +In Paperclip, **task** and **issue** refer to the same work item. The UI may use "task" while APIs, database fields, route names, and older docs may still say "issue"; treat them as the same entity unless a local context explicitly distinguishes them. + ## Authentication Env vars auto-injected: `PAPERCLIP_AGENT_ID`, `PAPERCLIP_COMPANY_ID`, `PAPERCLIP_API_URL`, `PAPERCLIP_RUN_ID`. Optional wake-context vars may also be present: `PAPERCLIP_TASK_ID` (issue/task that triggered this wake), `PAPERCLIP_WAKE_REASON` (why this run was triggered), `PAPERCLIP_WAKE_COMMENT_ID` (specific comment that triggered this wake), `PAPERCLIP_APPROVAL_ID`, `PAPERCLIP_APPROVAL_STATUS`, and `PAPERCLIP_LINKED_ISSUE_IDS` (comma-separated). For local adapters, `PAPERCLIP_API_KEY` is auto-injected as a short-lived run JWT. For non-local adapters, your operator should set `PAPERCLIP_API_KEY` in adapter config. All requests use `Authorization: Bearer $PAPERCLIP_API_KEY`. All endpoints under `/api`, all JSON. Never hard-code the API URL. diff --git a/tests/e2e/onboarding.spec.ts b/tests/e2e/onboarding.spec.ts index 2a6d6a43..44a2e82d 100644 --- a/tests/e2e/onboarding.spec.ts +++ b/tests/e2e/onboarding.spec.ts @@ -109,7 +109,7 @@ test.describe("Onboarding wizard", () => { await expect(page.locator("text=" + AGENT_NAME)).toBeVisible(); await expect(page.locator("text=" + TASK_TITLE)).toBeVisible(); - await page.getByRole("button", { name: "Create & Open Issue" }).click(); + await page.getByRole("button", { name: "Create & Open Task" }).click(); await expect(page).toHaveURL(/\/issues\//, { timeout: 30_000 }); diff --git a/tests/e2e/planning-mode-visual-verification.spec.ts b/tests/e2e/planning-mode-visual-verification.spec.ts index 8095c770..074de084 100644 --- a/tests/e2e/planning-mode-visual-verification.spec.ts +++ b/tests/e2e/planning-mode-visual-verification.spec.ts @@ -61,7 +61,7 @@ test("captures planning mode UI for desktop and mobile", async ({ page }) => { await page.getByRole("button", { name: "Next" }).click(); await expect(page.locator("h3", { hasText: "Ready to launch" })).toBeVisible({ timeout: 30_000 }); - await page.getByRole("button", { name: "Create & Open Issue" }).click(); + await page.getByRole("button", { name: "Create & Open Task" }).click(); await expect(page).toHaveURL(/\/issues\//, { timeout: 30_000 }); const openedIssueUrl = page.url(); diff --git a/tests/release-smoke/docker-auth-onboarding.spec.ts b/tests/release-smoke/docker-auth-onboarding.spec.ts index 497d993c..d4494599 100644 --- a/tests/release-smoke/docker-auth-onboarding.spec.ts +++ b/tests/release-smoke/docker-auth-onboarding.spec.ts @@ -69,7 +69,7 @@ test.describe("Docker authenticated onboarding smoke", () => { await expect(page.getByText(AGENT_NAME)).toBeVisible(); await expect(page.getByText(TASK_TITLE)).toBeVisible(); - await page.getByRole("button", { name: "Create & Open Issue" }).click(); + await page.getByRole("button", { name: "Create & Open Task" }).click(); await expect(page).toHaveURL(/\/issues\//, { timeout: 10_000 }); const baseUrl = new URL(page.url()).origin; diff --git a/ui/src/components/ActiveAgentsPanel.tsx b/ui/src/components/ActiveAgentsPanel.tsx index 4ff1c2ce..2c2b0fcf 100644 --- a/ui/src/components/ActiveAgentsPanel.tsx +++ b/ui/src/components/ActiveAgentsPanel.tsx @@ -27,7 +27,7 @@ function RunCardRecoveryChip({ action }: { action: IssueRecoveryAction }) { data-recovery-state={state} role="status" aria-label={tone.label} - title={`${tone.label} — open the source issue to act.`} + title={`${tone.label} — open the source task to act.`} className={cn( "inline-flex shrink-0 items-center gap-0.5 rounded-full border px-1.5 py-0.5 text-[10px] font-medium", tone.className, diff --git a/ui/src/components/ActivityCharts.tsx b/ui/src/components/ActivityCharts.tsx index 591bcc4e..bd3dd22a 100644 --- a/ui/src/components/ActivityCharts.tsx +++ b/ui/src/components/ActivityCharts.tsx @@ -144,7 +144,7 @@ export function PriorityChart({ issues }: { issues: { priority: string; createdA const maxValue = Math.max(...Array.from(grouped.values()).map(v => Object.values(v).reduce((a, b) => a + b, 0)), 1); const hasData = Array.from(grouped.values()).some(v => Object.values(v).reduce((a, b) => a + b, 0) > 0); - if (!hasData) return

No issues

; + if (!hasData) return

No tasks

; return (
@@ -211,7 +211,7 @@ export function IssueStatusChart({ issues }: { issues: { status: string; created const maxValue = Math.max(...Array.from(grouped.values()).map(v => Object.values(v).reduce((a, b) => a + b, 0)), 1); const hasData = allStatuses.size > 0; - if (!hasData) return

No issues

; + if (!hasData) return

No tasks

; return (
diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index e07e9734..9532ba5d 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -794,7 +794,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
setParentSearch(e.target.value)} autoFocus={!inline} @@ -1693,11 +1693,11 @@ export function IssueProperties({ <> setBlockedBySearch(e.target.value)} autoFocus={!inline} - aria-label="Search issues to add as blockers" + aria-label="Search tasks to add as blockers" />
+
Searching tasks...
) : blockerOptions.length === 0 ? ( -
No matching issues.
+
No matching tasks.
) : null}
@@ -1913,7 +1913,7 @@ export function IssueProperties({ ) : null} - +
{childIssues.length > 0 ? childIssues.map((child) => ( @@ -1927,7 +1927,7 @@ export function IssueProperties({ onClick={onAddSubIssue} > - Add sub-issue + Add sub-task ) : null}
diff --git a/ui/src/components/IssueRecoveryActionCard.test.tsx b/ui/src/components/IssueRecoveryActionCard.test.tsx index ea280927..a82a717b 100644 --- a/ui/src/components/IssueRecoveryActionCard.test.tsx +++ b/ui/src/components/IssueRecoveryActionCard.test.tsx @@ -146,7 +146,7 @@ describe("IssueRecoveryActionCard", () => { expect(node.textContent).toContain("RECOVERY NEEDED"); expect(node.textContent).toContain("Missing Disposition"); expect(node.textContent).not.toContain("missing_disposition"); - expect(node.textContent).toContain("This issue's run finished, but no next step was chosen."); + expect(node.textContent).toContain("This task's run finished, but no next step was chosen."); expect(node.textContent).toContain("ClaudeCoder"); expect(node.textContent).toContain("CodexCoder"); expect(node.textContent).toContain("Choose and record a valid issue disposition."); @@ -190,7 +190,7 @@ describe("IssueRecoveryActionCard", () => { expect(node.textContent).toContain("Workspace Validation"); expect(node.textContent).not.toContain("workspace_validation\n"); expect(node.textContent).toContain( - "Paperclip stopped this run because the issue's git workspace could not be validated.", + "Paperclip stopped this run because the task's git workspace could not be validated.", ); expect(node.textContent).toContain("Repair the source issue workspace link"); expect(node.textContent).toContain("Manual repair required"); @@ -212,7 +212,7 @@ describe("IssueRecoveryActionCard", () => { click(node.querySelector("[data-testid='recovery-action-resolve-trigger']")); expect(document.body.textContent).toContain("Try again"); - expect(document.body.textContent).toContain("Mark issue done"); + expect(document.body.textContent).toContain("Mark task done"); expect(document.body.textContent).not.toContain("Mark blocked"); expect(document.body.textContent).not.toContain("Delegate follow-up issue"); click([...document.body.querySelectorAll("button")].find((button) => button.textContent?.includes("Try again")) ?? null); @@ -227,7 +227,7 @@ describe("IssueRecoveryActionCard", () => { click(node.querySelector("[data-testid='recovery-action-resolve-trigger']")); expect(document.body.textContent).toContain("Try again"); - expect(document.body.textContent).toContain("Mark issue done"); + expect(document.body.textContent).toContain("Mark task done"); expect(document.body.textContent).toContain("Send for review"); expect(document.body.textContent).toContain("False positive, done"); expect(document.body.textContent).toContain("False positive, review"); diff --git a/ui/src/components/IssueRecoveryActionCard.tsx b/ui/src/components/IssueRecoveryActionCard.tsx index 8fa0663e..d086c3dc 100644 --- a/ui/src/components/IssueRecoveryActionCard.tsx +++ b/ui/src/components/IssueRecoveryActionCard.tsx @@ -45,22 +45,22 @@ export interface IssueRecoveryActionCardProps { const KIND_LABEL: Record = { missing_disposition: "Missing Disposition", - stranded_assigned_issue: "Stranded Issue", + stranded_assigned_issue: "Stranded Task", workspace_validation: "Workspace Validation", active_run_watchdog: "Active Watchdog", issue_graph_liveness: "Graph Liveness", }; const KIND_HEADLINE: Record = { - missing_disposition: "This issue's run finished, but no next step was chosen.", + missing_disposition: "This task's run finished, but no next step was chosen.", stranded_assigned_issue: - "Paperclip retried this issue's last run and it still has no live execution path.", + "Paperclip retried this task's last run and it still has no live execution path.", workspace_validation: - "Paperclip stopped this run because the issue's git workspace could not be validated.", + "Paperclip stopped this run because the task's git workspace could not be validated.", active_run_watchdog: "The active run has been silent. Recovery is observing without interrupting it.", issue_graph_liveness: - "Paperclip detected this issue lost a live action path. A recovery owner needs to act.", + "Paperclip detected this task lost a live action path. A recovery owner needs to act.", }; const STATE_TONE: Record {content} @@ -50,7 +50,7 @@ export function IssueReferencePill({ data-mention-kind="issue" className={classNames} title={issue.title} - aria-label={`Issue ${issueLabel}: ${issue.title}`} + aria-label={`Task ${issueLabel}: ${issue.title}`} > {content} diff --git a/ui/src/components/IssueRelatedWorkPanel.test.tsx b/ui/src/components/IssueRelatedWorkPanel.test.tsx index d1bd505f..a2273849 100644 --- a/ui/src/components/IssueRelatedWorkPanel.test.tsx +++ b/ui/src/components/IssueRelatedWorkPanel.test.tsx @@ -55,8 +55,8 @@ describe("IssueRelatedWorkPanel", () => { expect(html).toContain("Referenced by"); expect(html).toContain("PAP-22"); expect(html).toContain("PAP-33"); - expect(html).toContain('aria-label="Issue PAP-22: Downstream task"'); - expect(html).toContain('aria-label="Issue PAP-33: Upstream task"'); + expect(html).toContain('aria-label="Task PAP-22: Downstream task"'); + expect(html).toContain('aria-label="Task PAP-33: Upstream task"'); expect(html).toContain("plan"); expect(html).toContain("comment"); }); diff --git a/ui/src/components/IssueRelatedWorkPanel.tsx b/ui/src/components/IssueRelatedWorkPanel.tsx index 0e5bcd8d..b3072807 100644 --- a/ui/src/components/IssueRelatedWorkPanel.tsx +++ b/ui/src/components/IssueRelatedWorkPanel.tsx @@ -95,15 +95,15 @@ export function IssueRelatedWorkPanel({
); diff --git a/ui/src/components/IssueRow.tsx b/ui/src/components/IssueRow.tsx index 578dc9ad..e474770a 100644 --- a/ui/src/components/IssueRow.tsx +++ b/ui/src/components/IssueRow.tsx @@ -250,7 +250,7 @@ function renderRecoveryChip(action: IssueRecoveryAction, selected: boolean): Rea tone.className, selected ? "!border-muted-foreground !text-muted-foreground" : null, )} - title={`${label} — open the source issue to act.`} + title={`${label} — open the source task to act.`} > {label} diff --git a/ui/src/components/IssueRunLedger.tsx b/ui/src/components/IssueRunLedger.tsx index ca096787..4746aea0 100644 --- a/ui/src/components/IssueRunLedger.tsx +++ b/ui/src/components/IssueRunLedger.tsx @@ -75,7 +75,7 @@ const LIVENESS_COPY: Record = { completed: { label: "Completed", tone: "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300", - description: "Issue reached a terminal state.", + description: "Task reached a terminal state.", }, advanced: { label: "Advanced", @@ -95,7 +95,7 @@ const LIVENESS_COPY: Record = { blocked: { label: "Blocked", tone: "border-yellow-500/30 bg-yellow-500/10 text-yellow-700 dark:text-yellow-300", - description: "Run or issue declared a blocker.", + description: "Run or task declared a blocker.", }, failed: { label: "Failed", @@ -535,7 +535,7 @@ export function IssueRunLedgerContent({ }, [activityEvents, canRenderActivityEvents, ledgerRuns]); return ( -
+

Run ledger

@@ -678,8 +678,8 @@ export function IssueRunLedgerContent({ {feedItems.length === 0 ? (
{renderActivityEvent - ? "Runs and activity will appear here once this issue has history." - : "Historical runs without liveness metadata will appear here once linked to this issue."} + ? "Runs and activity will appear here once this task has history." + : "Historical runs without liveness metadata will appear here once linked to this task."}
) : (
diff --git a/ui/src/components/IssueSiblingNavigation.test.tsx b/ui/src/components/IssueSiblingNavigation.test.tsx index d356fcfb..abcbdab3 100644 --- a/ui/src/components/IssueSiblingNavigation.test.tsx +++ b/ui/src/components/IssueSiblingNavigation.test.tsx @@ -84,7 +84,7 @@ describe("IssueSiblingNavigation", () => { ); const nav = node.querySelector("nav"); - expect(nav?.getAttribute("aria-label")).toBe("Sub-issue navigation"); + expect(nav?.getAttribute("aria-label")).toBe("Sub-task navigation"); expect(nav?.className).toContain("sm:grid-cols-2"); expect(nav?.className).not.toContain("border-t"); @@ -93,13 +93,13 @@ describe("IssueSiblingNavigation", () => { expect(links[0].textContent).toContain("Previous"); expect(links[0].textContent).toContain("PAP-1"); expect(links[0].textContent).toContain("Previous sibling title"); - expect(links[0].getAttribute("aria-label")).toBe("Previous sub-issue: PAP-1 - Previous sibling title"); + expect(links[0].getAttribute("aria-label")).toBe("Previous sub-task: PAP-1 - Previous sibling title"); expect(links[0].getAttribute("data-quicklook-align")).toBe("start"); expect(links[1].textContent).toContain("Next"); expect(links[1].textContent).toContain("PAP-3"); expect(links[1].textContent).toContain("Next sibling title"); - expect(links[1].getAttribute("aria-label")).toBe("Next sub-issue: PAP-3 - Next sibling title"); + expect(links[1].getAttribute("aria-label")).toBe("Next sub-task: PAP-3 - Next sibling title"); expect(links[1].getAttribute("data-quicklook-align")).toBe("end"); expect(links[1].className).toContain("sm:text-right"); diff --git a/ui/src/components/IssueSiblingNavigation.tsx b/ui/src/components/IssueSiblingNavigation.tsx index 213dfe4d..f5d2e7e4 100644 --- a/ui/src/components/IssueSiblingNavigation.tsx +++ b/ui/src/components/IssueSiblingNavigation.tsx @@ -16,7 +16,7 @@ export function IssueSiblingNavigation({ navigation, linkState }: IssueSiblingNa return (
) : summary.totalCount === 0 ? ( -
No active sub-issues
+
No active sub-tasks
) : summary.doneCount === summary.totalCount ? ( -
All sub-issues done
+
All sub-tasks done
) : ( -
No actionable sub-issues
+
No actionable sub-tasks
)}
@@ -1289,8 +1289,8 @@ export function IssuesList({ viewState.groupBy, ]); - const createActionLabel = createIssueLabel ? `Create ${createIssueLabel}` : "Create Issue"; - const createButtonLabel = createIssueLabel ? `New ${createIssueLabel}` : "New Issue"; + const createActionLabel = createIssueLabel ? `Create ${createIssueLabel}` : "Create Task"; + const createButtonLabel = createIssueLabel ? `New ${createIssueLabel}` : "New Task"; const openCreateIssueDialog = useCallback((group?: { key: string; items: Issue[] }) => { openNewIssue(newIssueDefaults(group)); }, [newIssueDefaults, openNewIssue]); @@ -1461,7 +1461,7 @@ export function IssuesList({ visibleColumnSet={visibleIssueColumnSet} onToggleColumn={toggleIssueColumn} onResetColumns={() => setIssueColumns(DEFAULT_INBOX_ISSUE_COLUMNS)} - title="Choose which issue columns stay visible" + title="Choose which task columns stay visible" iconOnly /> @@ -1539,7 +1539,7 @@ export function IssuesList({ ["assignee", "Assignee"], ["project", "Project"], ["workspace", "Workspace"], - ["parent", "Parent Issue"], + ["parent", "Parent Task"], ["none", "None"], ] as const).map(([value, label]) => (
- Uses the shared chat-style run surface from issue activity. + Uses the shared chat-style run surface from task activity.
diff --git a/ui/src/components/MobileBottomNav.tsx b/ui/src/components/MobileBottomNav.tsx index 81006b59..e32a68b9 100644 --- a/ui/src/components/MobileBottomNav.tsx +++ b/ui/src/components/MobileBottomNav.tsx @@ -43,7 +43,7 @@ export function MobileBottomNav({ visible }: MobileBottomNavProps) { const items = useMemo( () => [ { type: "link", to: "/dashboard", label: "Home", icon: House }, - { type: "link", to: "/issues", label: "Issues", icon: CircleDot }, + { type: "link", to: "/issues", label: "Tasks", icon: CircleDot }, { type: "action", label: "Create", icon: SquarePen, onClick: () => openNewIssue() }, { type: "link", to: "/agents/all", label: "Agents", icon: Users }, { diff --git a/ui/src/components/NewIssueDialog.test.tsx b/ui/src/components/NewIssueDialog.test.tsx index b2627f72..ed9ddc90 100644 --- a/ui/src/components/NewIssueDialog.test.tsx +++ b/ui/src/components/NewIssueDialog.test.tsx @@ -352,11 +352,11 @@ describe("NewIssueDialog", () => { const { root } = renderDialog(container); await flush(); - expect(container.textContent).toContain("New sub-issue"); - expect(container.textContent).toContain("Sub-issue of"); + expect(container.textContent).toContain("New sub-task"); + expect(container.textContent).toContain("Sub-task of"); expect(container.textContent).toContain("PAP-1"); expect(container.textContent).toContain("Parent issue"); - expect(container.textContent).toContain("Create Sub-Issue"); + expect(container.textContent).toContain("Create Sub-Task"); act(() => root.unmount()); @@ -364,9 +364,9 @@ describe("NewIssueDialog", () => { const rerendered = renderDialog(container); await flush(); - expect(container.textContent).toContain("New issue"); - expect(container.textContent).toContain("Create Issue"); - expect(container.textContent).not.toContain("Sub-issue of"); + expect(container.textContent).toContain("New task"); + expect(container.textContent).toContain("Create Task"); + expect(container.textContent).not.toContain("Sub-task of"); act(() => rerendered.root.unmount()); }); @@ -421,7 +421,7 @@ describe("NewIssueDialog", () => { expect(mockExecutionWorkspacesApi.list).not.toHaveBeenCalled(); const submitButton = Array.from(container.querySelectorAll("button")) - .find((button) => button.textContent?.includes("Create Sub-Issue")); + .find((button) => button.textContent?.includes("Create Sub-Task")); expect(submitButton).not.toBeUndefined(); await waitForAssertion(() => { expect(submitButton?.hasAttribute("disabled")).toBe(false); @@ -460,7 +460,7 @@ describe("NewIssueDialog", () => { expect(planningButton?.className).toContain("bg-accent"); const submitButton = Array.from(container.querySelectorAll("button")) - .find((button) => button.textContent?.includes("Create Issue")); + .find((button) => button.textContent?.includes("Create Task")); expect(submitButton).not.toBeUndefined(); await vi.waitFor(() => { expect(submitButton?.hasAttribute("disabled")).toBe(false); @@ -531,14 +531,14 @@ describe("NewIssueDialog", () => { const { root } = renderDialog(container); await flush(); - expect(container.textContent).toContain("New issue"); - expect(container.textContent).not.toContain("New sub-issue"); + expect(container.textContent).toContain("New task"); + expect(container.textContent).not.toContain("New sub-task"); await waitForAssertion(() => { expect(container.textContent).toContain("Reusing PAP-100"); }); const submitButton = Array.from(container.querySelectorAll("button")) - .find((button) => button.textContent?.includes("Create Issue")); + .find((button) => button.textContent?.includes("Create Task")); expect(submitButton).not.toBeUndefined(); await act(async () => { @@ -578,7 +578,7 @@ describe("NewIssueDialog", () => { const { root } = renderDialog(container); await flush(); - const titleInput = container.querySelector('textarea[placeholder="Issue title"]') as HTMLTextAreaElement | null; + const titleInput = container.querySelector('textarea[placeholder="Task title"]') as HTMLTextAreaElement | null; const descriptionInput = container.querySelector('textarea[aria-label="Add description..."]') as HTMLTextAreaElement | null; expect(titleInput).not.toBeNull(); expect(descriptionInput).not.toBeNull(); @@ -601,7 +601,7 @@ describe("NewIssueDialog", () => { await flush(); const submitButton = Array.from(container.querySelectorAll("button")) - .find((button) => button.textContent?.includes("Create Issue")); + .find((button) => button.textContent?.includes("Create Task")); expect(submitButton).not.toBeUndefined(); await vi.waitFor(() => { expect(submitButton?.hasAttribute("disabled")).toBe(false); @@ -635,7 +635,7 @@ describe("NewIssueDialog", () => { const { root } = renderDialog(container); await flush(); - const titleInput = container.querySelector('textarea[placeholder="Issue title"]') as HTMLTextAreaElement | null; + const titleInput = container.querySelector('textarea[placeholder="Task title"]') as HTMLTextAreaElement | null; const descriptionInput = container.querySelector('textarea[aria-label="Add description..."]') as HTMLTextAreaElement | null; expect(titleInput).not.toBeNull(); expect(descriptionInput).not.toBeNull(); @@ -644,7 +644,7 @@ describe("NewIssueDialog", () => { await typeTextareaValue(descriptionInput!, description); const submitButton = Array.from(container.querySelectorAll("button")) - .find((button) => button.textContent?.includes("Create Issue")); + .find((button) => button.textContent?.includes("Create Task")); expect(submitButton).not.toBeUndefined(); await vi.waitFor(() => { expect(submitButton?.hasAttribute("disabled")).toBe(false); @@ -671,7 +671,7 @@ describe("NewIssueDialog", () => { const { root } = renderDialog(container); await flush(); - const titleInput = container.querySelector('textarea[placeholder="Issue title"]') as HTMLTextAreaElement | null; + const titleInput = container.querySelector('textarea[placeholder="Task title"]') as HTMLTextAreaElement | null; expect(titleInput).not.toBeNull(); await typeTextareaValue(titleInput!, "Plan this first"); @@ -683,7 +683,7 @@ describe("NewIssueDialog", () => { await flush(); const submitButton = Array.from(container.querySelectorAll("button")) - .find((button) => button.textContent?.includes("Create Issue")); + .find((button) => button.textContent?.includes("Create Task")); expect(submitButton).not.toBeUndefined(); await vi.waitFor(() => { expect(submitButton?.hasAttribute("disabled")).toBe(false); @@ -720,7 +720,7 @@ describe("NewIssueDialog", () => { await flush(); const submitButton = Array.from(container.querySelectorAll("button")) - .find((button) => button.textContent?.includes("Create Sub-Issue")); + .find((button) => button.textContent?.includes("Create Sub-Task")); expect(submitButton).not.toBeUndefined(); await act(async () => { @@ -754,7 +754,7 @@ describe("NewIssueDialog", () => { expect(dialogContent?.getAttribute("style")).toContain("env(safe-area-inset-top)"); expect(dialogContent?.getAttribute("style")).toContain("env(safe-area-inset-bottom)"); - const titleInput = container.querySelector('textarea[placeholder="Issue title"]'); + const titleInput = container.querySelector('textarea[placeholder="Task title"]'); const descriptionInput = container.querySelector('textarea[aria-label="Add description..."]'); const bodyScrollRegion = Array.from(container.querySelectorAll("div")).find((element) => typeof element.className === "string" && element.className.includes("overscroll-contain"), @@ -862,7 +862,7 @@ describe("NewIssueDialog", () => { await flush(); await flush(); - expect(container.textContent).not.toContain("will no longer use the parent issue workspace"); + expect(container.textContent).not.toContain("will no longer use the parent task workspace"); const selects = Array.from(container.querySelectorAll("select")); const modeSelect = selects[0] as HTMLSelectElement | undefined; @@ -874,7 +874,7 @@ describe("NewIssueDialog", () => { }); await flush(); - expect(container.textContent).toContain("will no longer use the parent issue workspace"); + expect(container.textContent).toContain("will no longer use the parent task workspace"); expect(container.textContent).toContain("Parent workspace"); act(() => root.unmount()); diff --git a/ui/src/components/NewIssueDialog.tsx b/ui/src/components/NewIssueDialog.tsx index 8354e336..4083f488 100644 --- a/ui/src/components/NewIssueDialog.tsx +++ b/ui/src/components/NewIssueDialog.tsx @@ -316,7 +316,7 @@ const IssueTitleTextarea = memo(function IssueTitleTextarea({ return (