diff --git a/packages/shared/src/types/issue.ts b/packages/shared/src/types/issue.ts index 0dc0bc030a..da94fbdd15 100644 --- a/packages/shared/src/types/issue.ts +++ b/packages/shared/src/types/issue.ts @@ -793,6 +793,7 @@ export interface Issue { ancestors?: IssueAncestor[]; title: string; description: string | null; + descriptionTruncated?: boolean; status: IssueStatus; workMode: IssueWorkMode; priority: IssuePriority; diff --git a/server/src/__tests__/issues-service.test.ts b/server/src/__tests__/issues-service.test.ts index 825e0048fd..ee0632e673 100644 --- a/server/src/__tests__/issues-service.test.ts +++ b/server/src/__tests__/issues-service.test.ts @@ -2590,11 +2590,65 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => { expect(result).toBeTruthy(); expect(result?.description).toHaveLength(1200); + expect(result?.descriptionTruncated).toBe(true); expect(result?.executionPolicy).toBeNull(); expect(result?.executionState).toBeNull(); expect(result?.executionWorkspaceSettings).toBeNull(); }); + it("marks list descriptions as not truncated when they fit the preview limit", async () => { + const companyId = randomUUID(); + const issueId = randomUUID(); + const description = "x".repeat(1200); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Exact preview issue", + description, + status: "todo", + priority: "medium", + }); + + const [result] = await svc.list(companyId); + + expect(result?.description).toHaveLength(1200); + expect(result?.descriptionTruncated).toBe(false); + }); + + it("marks null list descriptions as not truncated", async () => { + const companyId = randomUUID(); + const issueId = randomUUID(); + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Null description issue", + description: null, + status: "todo", + priority: "medium", + }); + + const [result] = await svc.list(companyId); + + expect(result?.description).toBeNull(); + expect(result?.descriptionTruncated).toBe(false); + }); + it("does not let description preview truncation split multibyte characters", async () => { const companyId = randomUUID(); const issueId = randomUUID(); @@ -2620,6 +2674,7 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => { expect(result?.description).toHaveLength(1200); expect(result?.description?.endsWith("—")).toBe(true); + expect(result?.descriptionTruncated).toBe(true); }); }); diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index e7500973b0..93a57137dc 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -3145,6 +3145,12 @@ const issueListSelect = { ) END `, + descriptionTruncated: sql` + CASE + WHEN ${issues.description} IS NULL THEN false + ELSE length(${issues.description}) > ${ISSUE_LIST_DESCRIPTION_MAX_CHARS} + END + `, status: issues.status, workMode: issues.workMode, harnessKind: issues.harnessKind,