diff --git a/server/src/__tests__/issue-display-status.test.ts b/server/src/__tests__/issue-display-status.test.ts index 3ce490c416b..e2f05c26720 100644 --- a/server/src/__tests__/issue-display-status.test.ts +++ b/server/src/__tests__/issue-display-status.test.ts @@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto"; import { eq, sql } from "drizzle-orm"; import { expect, it } from "vitest"; import request from "supertest"; -import { agents, heartbeatRuns, issueRecoveryActions, issues, statusCards } from "@paperclipai/db"; +import { agents, heartbeatRuns, issueRecoveryActions, issueRelations, issues, statusCards } from "@paperclipai/db"; import { companySearchQuerySchema, createIssueSchema, PROVIDER_QUOTA_MONITOR_SERVICE_NAME } from "@paperclipai/shared"; import { issueDisplayStatusSql } from "../services/issue-display-status.js"; import { issueService } from "../services/issues.js"; @@ -69,6 +69,17 @@ describeEmbeddedPostgres("Monitoring task presentation", () => { expect(await svc.count(f.companyId, { status: "in_progress" })).toBe(2); }); + it("shows the same wait indicator in list blocker summaries and task detail relations", async () => { + const f = await fixture(); + const [dependent] = await ctx.db.insert(issues).values({ companyId: f.companyId, title: "Ship after deployment", status: "blocked" }).returning(); + await ctx.db.insert(issueRelations).values({ companyId: f.companyId, issueId: f.issueId, relatedIssueId: dependent!.id, type: "blocks" }); + const svc = issueService(ctx.db); + const list = await svc.list(f.companyId, { includeBlockedBy: true }); + expect(list.find(row => row.id === dependent!.id)?.blockedBy).toEqual([expect.objectContaining({ id: f.issueId, displayStatus: "monitoring" })]); + const detail = await request(routeApp(ctx.db, f.actor, issueRoutes)).get(`/api/issues/${dependent!.id}`).expect(200); + expect(detail.body.blockedBy).toEqual([expect.objectContaining({ id: f.issueId, displayStatus: "monitoring" })]); + }); + it("returns to In Progress during the check and Monitoring after another yield, then Done", async () => { const f = await fixture(); const runId = randomUUID(); diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index b6c0a1f6e1c..d390be0475d 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -5286,6 +5286,7 @@ async function blockedByMapForIssues( identifier: issues.identifier, title: issues.title, status: issues.status, + displayStatus: issueDisplayStatusSql(), priority: issues.priority, assigneeAgentId: issues.assigneeAgentId, assigneeUserId: issues.assigneeUserId, diff --git a/ui/src/lib/recent-tasks.test.ts b/ui/src/lib/recent-tasks.test.ts index e7f24b7b731..d6577065491 100644 --- a/ui/src/lib/recent-tasks.test.ts +++ b/ui/src/lib/recent-tasks.test.ts @@ -102,6 +102,14 @@ describe("recent task persistence", () => { window.removeEventListener(RECENT_TASKS_UPDATED_EVENT, listener); }); + it("records a new display status without a task edit or newer activity", () => { + const task = { ...issue("1"), status: "in_progress" as const, displayStatus: "monitoring" as const }; + recordRecentTask(task, "user-1"); + recordRecentTask({ ...task, displayStatus: "in_progress" }, "user-1"); + const storageKey = getRecentTasksStorageKey("company-1", "user-1"); + expect(readRecentTasks(storageKey, "company-1")[0]?.displayStatus).toBe("in_progress"); + }); + it("prunes unavailable tasks and refreshes stored snapshots", () => { recordRecentTask(issue("1"), "user-1", 1); recordRecentTask(issue("2"), "user-1", 2); diff --git a/ui/src/lib/recent-tasks.ts b/ui/src/lib/recent-tasks.ts index b04b037fb74..1f3932c4817 100644 --- a/ui/src/lib/recent-tasks.ts +++ b/ui/src/lib/recent-tasks.ts @@ -166,6 +166,7 @@ export function recordRecentTask( && existing.title === entry.title && existing.identifier === entry.identifier && existing.status === entry.status + && existing.displayStatus === entry.displayStatus && existing.externalConversationState === entry.externalConversationState && existing.recordedAt === entry.recordedAt && existing.snapshotUpdatedAt === entry.snapshotUpdatedAt