feat(FN-3276): add task review tab with metadata persistence and desktop la

This merge lands three major features and a significant dashboard enhancement. FN-3276 adds a full Review tab to the task detail modal with multi-step lifecycle: review metadata persistence in the task store, new task workflow routes for refresh and same-task revision, and the review tab UI surface

Fusion-Task-Id: FN-3276
This commit is contained in:
Fusion
2026-05-07 21:28:56 -07:00
committed by gsxdsm
parent 11bf7084e1
commit 954ae1078d
23 changed files with 905 additions and 24 deletions

View File

@@ -4,7 +4,10 @@ import type { TaskStore, Task } from "@fusion/core";
const mockStore = {
addTaskComment: vi.fn<(id: string, text: string, author?: string) => Promise<Task>>(),
getTask: vi.fn<(id: string) => Promise<Task>>().mockResolvedValue({ id: "FN-001", review: undefined } as Task),
updateTask: vi.fn<(id: string, updates: Partial<Task>) => Promise<Task>>().mockResolvedValue({ id: "FN-001" } as Task),
createTask: vi.fn<(input: Parameters<TaskStore["createTask"]>[0]) => Promise<Task>>().mockResolvedValue({ id: "FN-123" } as Task),
moveTask: vi.fn<(id: string, column: Task["column"]) => Promise<Task>>().mockResolvedValue({ id: "FN-001", column: "in-progress" } as Task),
} as unknown as TaskStore;
describe("PrCommentHandler", () => {
@@ -195,6 +198,27 @@ describe("PrCommentHandler", () => {
});
});
describe("handleChangesRequested", () => {
it("persists review item feedback when changes are requested", async () => {
(mockStore.getTask as ReturnType<typeof vi.fn>).mockResolvedValue({ id: "FN-001", column: "in-review", review: undefined } as Task);
await handler.handleChangesRequested("FN-001", mockPrInfo, "reviewer", "Please add tests");
expect(mockStore.updateTask).toHaveBeenCalledWith(
"FN-001",
expect.objectContaining({
review: expect.objectContaining({
mode: "pull-request",
items: expect.arrayContaining([
expect.objectContaining({ source: "github-pr", status: "queued" }),
]),
}),
}),
);
expect(mockStore.moveTask).toHaveBeenCalledWith("FN-001", "in-progress");
});
});
describe("createFollowUpTask", () => {
it("creates follow-up task for unaddressed feedback", async () => {
await handler.createFollowUpTask("FN-001", mockPrInfo, [

View File

@@ -86,6 +86,7 @@ export class PrCommentHandler {
try {
await this.store.addTaskComment(taskId, text, "agent");
await this.upsertReviewItem(taskId, prInfo, comment, "queued");
prMonitorLog.log(`Added comment for PR review #${comment.id}`);
} catch (err) {
prMonitorLog.error(`Failed to add comment for ${taskId}:`, err);
@@ -179,6 +180,19 @@ export class PrCommentHandler {
].join("\n");
await this.store.addTaskComment(taskId, feedbackText, "agent");
await this.upsertReviewItem(
taskId,
prInfo,
{
id: Date.now(),
body: reviewBody || "(no review body)",
user: { login: reviewerLogin },
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
html_url: prInfo.url,
},
"queued",
);
await this.store.moveTask(taskId, "in-progress");
await this.store.logEntry(
taskId,
@@ -231,4 +245,51 @@ Please review the PR comments and address any remaining issues.`;
prMonitorLog.error(`Failed to create follow-up task:`, err);
}
}
private async upsertReviewItem(
taskId: string,
prInfo: PrInfo,
comment: PrComment,
status: "queued" | "in-progress" | "addressed" | "failed",
): Promise<void> {
const task = await this.store.getTask(taskId);
const now = new Date().toISOString();
const current = task.review ?? {
mode: "pull-request",
source: "github-pr",
decision: "pending",
items: [],
selectedItemIds: [],
};
const itemId = `gh-comment-${comment.id}`;
const existingIndex = current.items.findIndex((item: { id: string }) => item.id === itemId);
const nextItem = {
id: itemId,
source: "github-pr" as const,
status,
summary: comment.body.trim().slice(0, 160) || `Feedback from @${comment.user.login}`,
body: comment.body,
reviewer: comment.user.login,
commentUrl: comment.html_url,
createdAt: comment.created_at,
updatedAt: now,
};
const nextItems = [...current.items];
if (existingIndex >= 0) {
nextItems[existingIndex] = { ...nextItems[existingIndex], ...nextItem };
} else {
nextItems.push(nextItem);
}
await this.store.updateTask(taskId, {
review: {
...current,
mode: "pull-request",
source: "github-pr",
summary: `PR #${prInfo.number} feedback items: ${nextItems.length}`,
latestRefreshAt: now,
items: nextItems,
},
});
}
}