From c4ccb8c14fc614e2b9d8a9f11043a18286949af0 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 5 Jun 2026 23:03:41 +0000 Subject: [PATCH] fix: dedupe artifacts across cursor pages Co-Authored-By: Paperclip --- .../company-artifacts-service.test.ts | 13 +++++++++++ server/src/services/company-artifacts.ts | 22 ++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/server/src/__tests__/company-artifacts-service.test.ts b/server/src/__tests__/company-artifacts-service.test.ts index 151c91f516c..429c724b6fd 100644 --- a/server/src/__tests__/company-artifacts-service.test.ts +++ b/server/src/__tests__/company-artifacts-service.test.ts @@ -385,6 +385,19 @@ describeEmbeddedPostgres("companyArtifactsService", () => { cursor: firstPage.nextCursor ?? undefined, }); expect(secondPage.artifacts.map((artifact) => artifact.title)).toEqual(["Primary Cut", "notes.txt"]); + + const pageAfterPrimaryWorkProduct = await companyArtifactsService(db, storage).list(companyId, { limit: 3 }); + expect(pageAfterPrimaryWorkProduct.artifacts.map((artifact) => artifact.title)).toEqual([ + "Review Notes", + "direct-video.mp4", + "Primary Cut", + ]); + + const afterPrimaryCursor = await companyArtifactsService(db, storage).list(companyId, { + limit: 10, + cursor: pageAfterPrimaryWorkProduct.nextCursor ?? undefined, + }); + expect(afterPrimaryCursor.artifacts.map((artifact) => artifact.title)).toEqual(["notes.txt"]); }); it("deduplicates work product attachments beyond the work product fetch window", async () => { diff --git a/server/src/services/company-artifacts.ts b/server/src/services/company-artifacts.ts index 6352e990ea8..3e787dce542 100644 --- a/server/src/services/company-artifacts.ts +++ b/server/src/services/company-artifacts.ts @@ -227,23 +227,33 @@ export function companyArtifactsService(db: Db, storage?: StorageService) { const workProductAgent = alias(agents, "work_product_agent"); const workProductArtifactId = sql`concat('work_product:', ${issueWorkProducts.id})`; const workProductContentType = sql`coalesce(${issueWorkProducts.metadata}->>'contentType', '')`; - const workProductConditions: SQL[] = [ + const workProductBaseConditions: SQL[] = [ eq(issueWorkProducts.companyId, companyId), eq(issueWorkProducts.type, "artifact"), eq(issueWorkProducts.provider, "paperclip"), ]; + const workProductConditions: SQL[] = [...workProductBaseConditions]; const workProductCursor = cursorCondition(sql`${issueWorkProducts.updatedAt}`, workProductArtifactId, cursor); const workProductKind = contentTypeKindCondition(workProductContentType, query.kind); if (workProductCursor) workProductConditions.push(workProductCursor); - if (workProductKind) workProductConditions.push(workProductKind); - if (query.projectId) workProductConditions.push(eq(issues.projectId, query.projectId)); + if (workProductKind) { + workProductBaseConditions.push(workProductKind); + workProductConditions.push(workProductKind); + } + if (query.projectId) { + const projectCondition = eq(issues.projectId, query.projectId); + workProductBaseConditions.push(projectCondition); + workProductConditions.push(projectCondition); + } if (q) { - workProductConditions.push(sql`( + const searchCondition = sql`( ${issueWorkProducts.title} ILIKE ${q} ESCAPE '\\' OR coalesce(${issueWorkProducts.summary}, '') ILIKE ${q} ESCAPE '\\' OR coalesce(${issues.identifier}, '') ILIKE ${q} ESCAPE '\\' OR ${issues.title} ILIKE ${q} ESCAPE '\\' - )`); + )`; + workProductBaseConditions.push(searchCondition); + workProductConditions.push(searchCondition); } const workProductRows = await db @@ -289,7 +299,7 @@ export function companyArtifactsService(db: Db, storage?: StorageService) { }) .from(issueWorkProducts) .innerJoin(issues, eq(issueWorkProducts.issueId, issues.id)) - .where(and(...workProductConditions, sql`${issueWorkProducts.metadata}->>'attachmentId' IS NOT NULL`)); + .where(and(...workProductBaseConditions, sql`${issueWorkProducts.metadata}->>'attachmentId' IS NOT NULL`)); for (const row of workProductAttachmentRows) { if (row.attachmentId) {