From 8ad9a29f069c0b5c3af2ff3f37e3a21467ada4cb Mon Sep 17 00:00:00 2001 From: Kaito Date: Sat, 16 May 2026 14:39:59 +0700 Subject: [PATCH 1/2] fix(api): hide archived task dependencies --- apps/web/app/api/agent/projects/route.ts | 4 +- .../web/app/api/agent/tasks/[taskId]/route.ts | 8 +- apps/web/app/api/agent/tasks/route.ts | 8 +- apps/web/lib/api/task-dependencies.ts | 11 ++- apps/web/lib/api/task-visibility.test.ts | 73 +++++++++++++++++++ 5 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 apps/web/lib/api/task-visibility.test.ts diff --git a/apps/web/app/api/agent/projects/route.ts b/apps/web/app/api/agent/projects/route.ts index 6658c43..4a16bfd 100644 --- a/apps/web/app/api/agent/projects/route.ts +++ b/apps/web/app/api/agent/projects/route.ts @@ -112,13 +112,13 @@ export async function GET(request: NextRequest) { archivedAt: true, blockedByDependencies: { select: { - dependencyTask: { select: { id: true, name: true, status: true } }, + dependencyTask: { select: { id: true, name: true, status: true, archivedAt: true } }, }, orderBy: { createdAt: "asc" }, }, unblocksDependencies: { select: { - blockedTask: { select: { id: true, name: true, status: true } }, + blockedTask: { select: { id: true, name: true, status: true, archivedAt: true } }, }, orderBy: { createdAt: "asc" }, }, diff --git a/apps/web/app/api/agent/tasks/[taskId]/route.ts b/apps/web/app/api/agent/tasks/[taskId]/route.ts index aae15de..f7b6291 100644 --- a/apps/web/app/api/agent/tasks/[taskId]/route.ts +++ b/apps/web/app/api/agent/tasks/[taskId]/route.ts @@ -100,13 +100,13 @@ export async function GET(request: NextRequest, { params }: RouteContext) { archivedAt: true, blockedByDependencies: { select: { - dependencyTask: { select: { id: true, name: true, status: true } }, + dependencyTask: { select: { id: true, name: true, status: true, archivedAt: true } }, }, orderBy: { createdAt: "asc" }, }, unblocksDependencies: { select: { - blockedTask: { select: { id: true, name: true, status: true } }, + blockedTask: { select: { id: true, name: true, status: true, archivedAt: true } }, }, orderBy: { createdAt: "asc" }, }, @@ -389,13 +389,13 @@ export async function PATCH(request: NextRequest, { params }: RouteContext) { archivedAt: true, blockedByDependencies: { select: { - dependencyTask: { select: { id: true, name: true, status: true } }, + dependencyTask: { select: { id: true, name: true, status: true, archivedAt: true } }, }, orderBy: { createdAt: "asc" }, }, unblocksDependencies: { select: { - blockedTask: { select: { id: true, name: true, status: true } }, + blockedTask: { select: { id: true, name: true, status: true, archivedAt: true } }, }, orderBy: { createdAt: "asc" }, }, diff --git a/apps/web/app/api/agent/tasks/route.ts b/apps/web/app/api/agent/tasks/route.ts index 8b23b33..9cec361 100644 --- a/apps/web/app/api/agent/tasks/route.ts +++ b/apps/web/app/api/agent/tasks/route.ts @@ -113,13 +113,13 @@ export async function GET(request: NextRequest) { archivedAt: true, blockedByDependencies: { select: { - dependencyTask: { select: { id: true, name: true, status: true } }, + dependencyTask: { select: { id: true, name: true, status: true, archivedAt: true } }, }, orderBy: { createdAt: "asc" }, }, unblocksDependencies: { select: { - blockedTask: { select: { id: true, name: true, status: true } }, + blockedTask: { select: { id: true, name: true, status: true, archivedAt: true } }, }, orderBy: { createdAt: "asc" }, }, @@ -346,13 +346,13 @@ export async function POST(request: NextRequest) { archivedAt: true, blockedByDependencies: { select: { - dependencyTask: { select: { id: true, name: true, status: true } }, + dependencyTask: { select: { id: true, name: true, status: true, archivedAt: true } }, }, orderBy: { createdAt: "asc" }, }, unblocksDependencies: { select: { - blockedTask: { select: { id: true, name: true, status: true } }, + blockedTask: { select: { id: true, name: true, status: true, archivedAt: true } }, }, orderBy: { createdAt: "asc" }, }, diff --git a/apps/web/lib/api/task-dependencies.ts b/apps/web/lib/api/task-dependencies.ts index 5eee08a..afbad56 100644 --- a/apps/web/lib/api/task-dependencies.ts +++ b/apps/web/lib/api/task-dependencies.ts @@ -4,6 +4,7 @@ type DependencyTask = { id: string name: string status: Status + archivedAt?: Date | null } type DependencyEdge = { @@ -21,8 +22,14 @@ export type TaskDependencyPayload = { export function serializeTaskDependencies(task: T) { const { blockedByDependencies, unblocksDependencies, ...rest } = task - const dependencies = blockedByDependencies?.map((dependency) => dependency.dependencyTask) ?? [] - const unblocks = unblocksDependencies?.map((dependency) => dependency.blockedTask) ?? [] + const dependencies = + blockedByDependencies + ?.map((dependency) => dependency.dependencyTask) + .filter((dependency) => !dependency.archivedAt) ?? [] + const unblocks = + unblocksDependencies + ?.map((dependency) => dependency.blockedTask) + .filter((dependency) => !dependency.archivedAt) ?? [] return { ...rest, diff --git a/apps/web/lib/api/task-visibility.test.ts b/apps/web/lib/api/task-visibility.test.ts new file mode 100644 index 0000000..edba687 --- /dev/null +++ b/apps/web/lib/api/task-visibility.test.ts @@ -0,0 +1,73 @@ +import assert from "node:assert/strict" +import { describe, it } from "node:test" + +import { Status } from "@/generated/prisma/enums" + +import { serializeTaskDependencies } from "./task-dependencies" + +describe("Agent API task visibility", () => { + it("omits archived dependency and unblocks tasks from serialized task payloads", () => { + const activeDependency = { + id: "dependency-active", + name: "Active dependency", + status: Status.done, + archivedAt: null, + } + const archivedDependency = { + id: "dependency-archived", + name: "Archived dependency", + status: Status.done, + archivedAt: new Date("2026-05-16T07:00:00.000Z"), + } + const activeBlockedTask = { + id: "blocked-active", + name: "Active blocked task", + status: Status.todo, + archivedAt: null, + } + const archivedBlockedTask = { + id: "blocked-archived", + name: "Archived blocked task", + status: Status.todo, + archivedAt: new Date("2026-05-16T07:00:00.000Z"), + } + + const result = serializeTaskDependencies({ + id: "task-1", + blockedByDependencies: [ + { dependencyTask: activeDependency }, + { dependencyTask: archivedDependency }, + ], + unblocksDependencies: [ + { blockedTask: activeBlockedTask }, + { blockedTask: archivedBlockedTask }, + ], + }) + + assert.deepEqual(result.dependencies, [activeDependency]) + assert.deepEqual(result.dependencyIds, [activeDependency.id]) + assert.deepEqual(result.unblocks, [activeBlockedTask]) + assert.equal(result.isDependencyReady, true) + }) + + it("does not mark dependency readiness from archived dependencies", () => { + const result = serializeTaskDependencies({ + id: "task-1", + blockedByDependencies: [ + { + dependencyTask: { + id: "dependency-archived", + name: "Archived dependency", + status: Status.done, + archivedAt: new Date("2026-05-16T07:00:00.000Z"), + }, + }, + ], + unblocksDependencies: [], + }) + + assert.deepEqual(result.dependencies, []) + assert.deepEqual(result.dependencyIds, []) + assert.equal(result.isDependencyReady, false) + }) +}) From 8b7773dc882e869b304c5ec1a059121d595e3b5d Mon Sep 17 00:00:00 2001 From: Kaito Date: Sat, 16 May 2026 16:04:55 +0700 Subject: [PATCH 2/2] fix(api): strip archivedAt from dependency summaries --- apps/web/lib/api/task-dependencies.ts | 22 +++++++++--- apps/web/lib/api/task-visibility.test.ts | 43 ++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/apps/web/lib/api/task-dependencies.ts b/apps/web/lib/api/task-dependencies.ts index afbad56..0e31340 100644 --- a/apps/web/lib/api/task-dependencies.ts +++ b/apps/web/lib/api/task-dependencies.ts @@ -7,6 +7,8 @@ type DependencyTask = { archivedAt?: Date | null } +type SerializedDependencyTask = Omit + type DependencyEdge = { dependencyTask: DependencyTask } @@ -24,12 +26,12 @@ export function serializeTaskDependencies(task: const { blockedByDependencies, unblocksDependencies, ...rest } = task const dependencies = blockedByDependencies - ?.map((dependency) => dependency.dependencyTask) - .filter((dependency) => !dependency.archivedAt) ?? [] + ?.map((dependency) => serializeDependencyTask(dependency.dependencyTask)) + .filter((dependency): dependency is SerializedDependencyTask => dependency !== null) ?? [] const unblocks = unblocksDependencies - ?.map((dependency) => dependency.blockedTask) - .filter((dependency) => !dependency.archivedAt) ?? [] + ?.map((dependency) => serializeDependencyTask(dependency.blockedTask)) + .filter((dependency): dependency is SerializedDependencyTask => dependency !== null) ?? [] return { ...rest, @@ -40,6 +42,18 @@ export function serializeTaskDependencies(task: } } +function serializeDependencyTask(task: DependencyTask): SerializedDependencyTask | null { + if (task.archivedAt) { + return null + } + + return { + id: task.id, + name: task.name, + status: task.status, + } +} + export function hasDependencyCycle( edges: Array<{ blockedTaskId: string; dependencyTaskId: string }>, blockedTaskId: string, diff --git a/apps/web/lib/api/task-visibility.test.ts b/apps/web/lib/api/task-visibility.test.ts index edba687..a53d674 100644 --- a/apps/web/lib/api/task-visibility.test.ts +++ b/apps/web/lib/api/task-visibility.test.ts @@ -44,12 +44,51 @@ describe("Agent API task visibility", () => { ], }) - assert.deepEqual(result.dependencies, [activeDependency]) + assert.deepEqual(result.dependencies, [ + { id: activeDependency.id, name: activeDependency.name, status: activeDependency.status }, + ]) assert.deepEqual(result.dependencyIds, [activeDependency.id]) - assert.deepEqual(result.unblocks, [activeBlockedTask]) + assert.deepEqual(result.unblocks, [ + { id: activeBlockedTask.id, name: activeBlockedTask.name, status: activeBlockedTask.status }, + ]) assert.equal(result.isDependencyReady, true) }) + it("omits archivedAt from serialized active dependency and unblocks tasks", () => { + const result = serializeTaskDependencies({ + id: "task-1", + blockedByDependencies: [ + { + dependencyTask: { + id: "dependency-active", + name: "Active dependency", + status: Status.done, + archivedAt: null, + }, + }, + ], + unblocksDependencies: [ + { + blockedTask: { + id: "blocked-active", + name: "Active blocked task", + status: Status.todo, + archivedAt: null, + }, + }, + ], + }) + + assert.deepEqual(result.dependencies, [ + { id: "dependency-active", name: "Active dependency", status: Status.done }, + ]) + assert.deepEqual(result.unblocks, [ + { id: "blocked-active", name: "Active blocked task", status: Status.todo }, + ]) + assert.equal("archivedAt" in result.dependencies[0], false) + assert.equal("archivedAt" in result.unblocks[0], false) + }) + it("does not mark dependency readiness from archived dependencies", () => { const result = serializeTaskDependencies({ id: "task-1",