From 1bb220b2eb6187fbffb8249416270d90dd036cf7 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Tue, 10 Mar 2026 16:07:13 -0700 Subject: [PATCH] feat(notes): enable web review decisions --- web/src/app/(app)/reviews/page.tsx | 38 ++++++++++++++++++++++- web/src/components/ProposalReviewCard.tsx | 38 ++++++++++++++++++++++- web/src/lib/review-client.ts | 29 +++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/web/src/app/(app)/reviews/page.tsx b/web/src/app/(app)/reviews/page.tsx index a474574..7d7f171 100644 --- a/web/src/app/(app)/reviews/page.tsx +++ b/web/src/app/(app)/reviews/page.tsx @@ -4,12 +4,13 @@ import { useEffect, useMemo, useState } from "react"; import { AppShell } from "@/components/AppShell"; import { AgentTimeline } from "@/components/AgentTimeline"; import { ProposalReviewCard } from "@/components/ProposalReviewCard"; -import { listAgentTimeline, listApprovalQueue } from "@/lib/review-client"; +import { approveReviewItem, listAgentTimeline, listApprovalQueue, rejectReviewItem } from "@/lib/review-client"; import type { AgentTimelineItem, ApprovalQueueItem } from "@/lib/types"; export default function ReviewsPage() { const [approvalQueue, setApprovalQueue] = useState([]); const [timeline, setTimeline] = useState([]); + const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); useEffect(() => { @@ -47,6 +48,38 @@ export default function ReviewsPage() { }, ] as const; + async function handleDecision(decision: "approved" | "rejected") { + if (!featuredProposal) { + return; + } + + setIsSubmitting(true); + + try { + const updated = + decision === "approved" + ? await approveReviewItem(featuredProposal) + : await rejectReviewItem(featuredProposal); + + setApprovalQueue((current) => current.filter((item) => item.id !== featuredProposal.id)); + setTimeline((current) => [ + { + id: updated.id, + actor: updated.owner, + action: `human ${decision === "approved" ? "approved" : "rejected"} proposal`, + timestamp: new Date().toISOString(), + status: updated.status, + summary: updated.after ?? updated.title, + }, + ...current, + ]); + } catch (err) { + setError(err instanceof Error ? err.message : "Unable to update review state"); + } finally { + setIsSubmitting(false); + } + } + return ( void handleDecision("approved")} + onReject={() => void handleDecision("rejected")} + isSubmitting={isSubmitting} /> ) : null} diff --git a/web/src/components/ProposalReviewCard.tsx b/web/src/components/ProposalReviewCard.tsx index 13239c6..6271fd9 100644 --- a/web/src/components/ProposalReviewCard.tsx +++ b/web/src/components/ProposalReviewCard.tsx @@ -2,16 +2,52 @@ export function ProposalReviewCard({ title, before, after, + onApprove, + onReject, + isSubmitting = false, }: { title: string; before: string; after: string; + onApprove?: () => void; + onReject?: () => void; + isSubmitting?: boolean; }) { return (
{title}
- before / after +
+ before / after + {onReject ? ( + + ) : null} + {onApprove ? ( + + ) : null} +
diff --git a/web/src/lib/review-client.ts b/web/src/lib/review-client.ts index c469de4..e65e1f2 100644 --- a/web/src/lib/review-client.ts +++ b/web/src/lib/review-client.ts @@ -77,6 +77,25 @@ function toApprovalQueueItem(action: NoteAgentActionDoc): ApprovalQueueItem { }; } +async function updateAgentActionState( + id: string, + workspaceId: string, + state: "approved" | "rejected", +): Promise { + const api = createNotesApiClient(); + + return api.fetch( + `/note-agent-actions/${encodeURIComponent(id)}?workspaceId=${encodeURIComponent(workspaceId)}`, + { + method: "PATCH", + body: JSON.stringify({ + state, + reviewedAt: new Date().toISOString(), + }), + }, + ); +} + async function listAgentActionsForWorkspace(workspaceId: string): Promise { const api = createNotesApiClient(); const response = await api.fetch( @@ -110,3 +129,13 @@ export async function listAgentTimeline(noteId?: string): Promise b.updatedAt.localeCompare(a.updatedAt)) .map(toTimelineItem); } + +export async function approveReviewItem(item: ApprovalQueueItem): Promise { + const updated = await updateAgentActionState(item.id, item.workspaceId, "approved"); + return toApprovalQueueItem(updated); +} + +export async function rejectReviewItem(item: ApprovalQueueItem): Promise { + const updated = await updateAgentActionState(item.id, item.workspaceId, "rejected"); + return toApprovalQueueItem(updated); +}