diff --git a/server/src/__tests__/status-card-evaluation-work.test.ts b/server/src/__tests__/status-card-evaluation-work.test.ts index c272dd4cc2e..d980c03249d 100644 --- a/server/src/__tests__/status-card-evaluation-work.test.ts +++ b/server/src/__tests__/status-card-evaluation-work.test.ts @@ -675,6 +675,22 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu expect(wakeup).toHaveBeenCalledOnce(); }); + it("labels an interval writeback by its refresh deadline despite earlier membership expiry", async () => { + const target = await issueService(db).create(companyId, { title: "Interval blocker", status: "blocked", priority: "medium" }); + const updatedAt = new Date(Date.now() - 59 * 60_000); + await db.update(issues).set({ updatedAt }).where(eq(issues.id, target.id)); + compiledQuery = companySearchQuerySchema.parse({ scope: "issues", status: ["blocked"], updatedWithin: "1h" }); + let membership: Date | null = null; + await companySearch.companySearchService(db).search(companyId, compiledQuery, { + issueResultsOnly: true, onNextMembershipChange: at => { membership = at; }, + }); + expect(membership).toEqual(new Date(updatedAt.getTime() + 3_600_001)); + const created = await card("interval"); + expect(created.nextEvalAt).toEqual(new Date(created.lastGeneratedAt!.getTime() + 15 * 60_000)); + expect(created.nextEvalAt!.getTime()).toBeGreaterThan(updatedAt.getTime() + 3_600_001); + expect(created.nextEvalReason).toBe("refresh"); + }); + it("uses expiry of an off-page candidate to refresh offset-ranked query membership", async () => { const now = new Date(); const tasks = await Promise.all((["critical", "high", "medium"] as const).map(priority => issueService(db).create(companyId, { diff --git a/server/src/services/status-cards.ts b/server/src/services/status-cards.ts index 97d776e9bd2..d4fe9d7ce80 100644 --- a/server/src/services/status-cards.ts +++ b/server/src/services/status-cards.ts @@ -1002,7 +1002,7 @@ export function statusCardService( pendingChangeCount: 0, pendingChangeHash: null, lastChangeAt: null, - nextEvalReason: current.refreshPolicy.mode === "reactive" ? "refresh" : membershipAt ? "membership" : null, + nextEvalReason: current.refreshPolicy.mode === "reactive" || current.refreshPolicy.mode === "interval" ? "refresh" : membershipAt ? "membership" : null, nextEvalAt: current.refreshPolicy.mode === "reactive" ? now : nextEvaluationAt(current, membershipAt, now), updatedAt: now, }).where(and(eq(statusCards.id, current.id), eq(statusCards.generatingIssueId, input.generationIssueId))).returning();