diff --git a/apps/web/app/dashboard/notes/actions.ts b/apps/web/app/dashboard/notes/actions.ts index 429ffb6..94fcc77 100644 --- a/apps/web/app/dashboard/notes/actions.ts +++ b/apps/web/app/dashboard/notes/actions.ts @@ -31,8 +31,6 @@ export async function markDoneTaskSummaryReadAction( }, select: { id: true, - summaryUpdatedAt: true, - taskUpdatedAt: true, project: { select: { companyId: true } }, }, }) @@ -49,30 +47,21 @@ export async function markDoneTaskSummaryReadAction( const readAt = new Date() - await prisma.$transaction(async (tx) => { - if (!task.summaryUpdatedAt) { - await tx.task.update({ - where: { id: task.id }, - data: { summaryUpdatedAt: task.taskUpdatedAt }, - }) - } - - await tx.taskReadMarker.upsert({ - where: { - taskId_agentId_status: { - taskId: task.id, - agentId: reviewReader.id, - status: Status.done, - }, - }, - create: { + await prisma.taskReadMarker.upsert({ + where: { + taskId_agentId_status: { taskId: task.id, agentId: reviewReader.id, status: Status.done, - readAt, }, - update: { readAt }, - }) + }, + create: { + taskId: task.id, + agentId: reviewReader.id, + status: Status.done, + readAt, + }, + update: { readAt }, }) await invalidateCompanyCache(task.project.companyId) diff --git a/apps/web/app/dashboard/notes/page.tsx b/apps/web/app/dashboard/notes/page.tsx index 608f6a3..9501b7b 100644 --- a/apps/web/app/dashboard/notes/page.tsx +++ b/apps/web/app/dashboard/notes/page.tsx @@ -10,6 +10,7 @@ import { } from "@/components/ui/card" import { findReviewReader, getReviewReaderLabel } from "@/lib/api/review-reader" import { getDashboardContext } from "@/lib/dashboard/companies" +import { isDoneSummaryUnread } from "@/lib/dashboard/note-read-state" import { prisma } from "@/lib/prisma" import { NoteList } from "./note-list" @@ -43,7 +44,6 @@ export default async function NotesPage({ searchParams }: NotesPageProps) { status: true, note: true, summaryUpdatedAt: true, - taskUpdatedAt: true, assigned: { select: { id: true, @@ -70,9 +70,7 @@ export default async function NotesPage({ searchParams }: NotesPageProps) { const unreadNotes = notes.filter((task) => { const readAt = task.readMarkers[0]?.readAt - const summaryUpdatedAt = task.summaryUpdatedAt ?? task.taskUpdatedAt - - return !readAt || readAt < summaryUpdatedAt + return isDoneSummaryUnread({ readAt, summaryUpdatedAt: task.summaryUpdatedAt }) }) return ( @@ -99,7 +97,7 @@ export default async function NotesPage({ searchParams }: NotesPageProps) { name: task.name, status: task.status, note: task.note ?? "", - summaryUpdatedAt: (task.summaryUpdatedAt ?? task.taskUpdatedAt).toISOString(), + summaryUpdatedAt: task.summaryUpdatedAt?.toISOString() ?? null, assigned: task.assigned, project: task.project, }))} diff --git a/apps/web/lib/dashboard/note-read-state.test.ts b/apps/web/lib/dashboard/note-read-state.test.ts new file mode 100644 index 0000000..6976347 --- /dev/null +++ b/apps/web/lib/dashboard/note-read-state.test.ts @@ -0,0 +1,35 @@ +import assert from "node:assert/strict" +import { describe, it } from "node:test" + +import { isDoneSummaryUnread } from "./note-read-state" + +describe("dashboard done summary read state", () => { + it("treats noted done tasks with unknown summary freshness as unread", () => { + assert.equal( + isDoneSummaryUnread({ + readAt: new Date("2026-05-15T12:00:00.000Z"), + summaryUpdatedAt: null, + }), + true + ) + }) + + it("compares read markers only against stored summary freshness", () => { + const summaryUpdatedAt = new Date("2026-05-15T12:00:00.000Z") + + assert.equal( + isDoneSummaryUnread({ + readAt: new Date("2026-05-15T11:59:59.000Z"), + summaryUpdatedAt, + }), + true + ) + assert.equal( + isDoneSummaryUnread({ + readAt: new Date("2026-05-15T12:00:00.000Z"), + summaryUpdatedAt, + }), + false + ) + }) +}) diff --git a/apps/web/lib/dashboard/note-read-state.ts b/apps/web/lib/dashboard/note-read-state.ts new file mode 100644 index 0000000..4e494f9 --- /dev/null +++ b/apps/web/lib/dashboard/note-read-state.ts @@ -0,0 +1,9 @@ +export function isDoneSummaryUnread({ + readAt, + summaryUpdatedAt, +}: { + readAt?: Date | null + summaryUpdatedAt?: Date | null +}) { + return !readAt || !summaryUpdatedAt || readAt < summaryUpdatedAt +} diff --git a/package.json b/package.json index 6db889d..ab26a34 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "prisma:studio": "prisma studio", "typecheck": "corepack pnpm prisma:generate && corepack pnpm --filter @agentbridge/web typecheck && corepack pnpm --filter agentbridge-ai typecheck", "typecheck:web": "corepack pnpm prisma:generate && corepack pnpm --filter @agentbridge/web typecheck", - "test:api": "tsx --tsconfig apps/web/tsconfig.json --test apps/web/lib/api/*.test.ts", + "test:api": "tsx --tsconfig apps/web/tsconfig.json --test apps/web/lib/api/*.test.ts apps/web/lib/dashboard/*.test.ts", "test:task-freshness": "tsx --tsconfig apps/web/tsconfig.json --test apps/web/lib/api/task-freshness.test.ts", "cli:dev": "corepack pnpm --filter agentbridge-ai dev", "cli:build": "corepack pnpm --filter agentbridge-ai build",