From 29ec6f3ab38a4029460879d15b97c3c77f118c28 Mon Sep 17 00:00:00 2001 From: Joseph Zaki <325440518+jz-krono@users.noreply.github.com> Date: Sun, 4 Oct 2026 06:09:59 +0900 Subject: [PATCH] fix: bind reviewed-page retries to saved draft --- README.md | 2 +- src/client/PageReviewCard.tsx | 77 +++++++++++++++++------------- src/client/page-review-decision.ts | 31 ++++++++++-- src/server/page-routes.ts | 7 +-- src/server/pages.ts | 66 ++++++++++++++++++------- src/shared/page-review.ts | 1 + tests/page-review.test.tsx | 32 ++++++++++++- tests/page-routes.test.ts | 47 ++++++++++++++++++ tests/pages.test.ts | 57 ++++++++++++++++++++++ 9 files changed, 259 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 49dfde40..40c27ba7 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ _Ask Scout to open a website, summarize it, save notes, and verify the file. Eve ### Review before saving -Ask a Dot to show a draft before saving it. A CopilotKit human-in-the-loop card pauses the conversation for **Approve & save** or **Decline**. Approval creates the page in an authorized Space and returns a link; retries recover the same saved page. The agent continues after your decision. +Ask a Dot to show a draft before saving it. A CopilotKit human-in-the-loop card pauses the conversation for **Approve & save** or **Decline**. Approval creates the page in an authorized Space and returns a link; retries with the same draft recover that saved page. A changed draft needs a new review. The agent continues after your decision. ### Text and calls diff --git a/src/client/PageReviewCard.tsx b/src/client/PageReviewCard.tsx index 9962ce4b..4bf2582c 100644 --- a/src/client/PageReviewCard.tsx +++ b/src/client/PageReviewCard.tsx @@ -2,14 +2,16 @@ import { useEffect, useRef, useState } from 'react'; import { Check, FileText, ArrowUpRight } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import { pageReviewSchema } from '../shared/page-review'; -import { decidePageReview, restorePageReview } from './page-review-decision'; -import { computerToolResult } from './ComputerToolCard'; +import { + decidePageReview, + matchesReviewedDraft, + restorePageReview, +} from './page-review-decision'; import { openPageLink } from './page-navigation'; -import type { Page } from '../server/pages'; +import type { ReviewedPage } from '../server/pages'; export function PageReviewCard({ args, status, - result, respond, threadId, toolCallId, @@ -24,25 +26,21 @@ export function PageReviewCard({ onSaved: () => void; }) { const draft = pageReviewSchema.safeParse(args); - const outcome = computerToolResult(result); - const [savedPage, setSavedPage] = useState(); + const [savedPage, setSavedPage] = useState(); const [error, setError] = useState(''); const [busy, setBusy] = useState(false); const [receiptReady, setReceiptReady] = useState(false); const [restoreAttempt, setRestoreAttempt] = useState(0); const pending = useRef(false); const finished = status === 'complete'; - const recordedApproval = outcome.approved === true; - const saved = !!savedPage || recordedApproval; - const pageId = - savedPage?.id ?? (typeof outcome.pageId === 'string' ? outcome.pageId : ''); - const spaceId = - savedPage?.spaceId ?? - (typeof outcome.spaceId === 'string' ? outcome.spaceId : ''); + const conflict = !!savedPage && !matchesReviewedDraft(savedPage, args); + const saved = !!savedPage && !conflict; + const pageId = savedPage?.id ?? ''; + const spaceId = savedPage?.spaceId ?? ''; useEffect(() => { - if (recordedApproval) return; let active = true; setReceiptReady(false); + setSavedPage(undefined); setError(''); void restorePageReview(threadId, toolCallId) .then((page) => { @@ -61,9 +59,9 @@ export function PageReviewCard({ return () => { active = false; }; - }, [threadId, toolCallId, recordedApproval, restoreAttempt]); + }, [threadId, toolCallId, restoreAttempt]); const decide = async (approved: boolean) => { - if (!respond || !receiptReady || pending.current) return; + if (!respond || !receiptReady || conflict || pending.current) return; pending.current = true; setBusy(true); setError(''); @@ -101,22 +99,26 @@ export function PageReviewCard({
- {saved - ? 'Saved to your Space' - : !receiptReady - ? 'Checking saved review…' - : finished - ? 'Review ended' - : 'Ready for your review'} + {conflict + ? 'Review changed' + : saved + ? 'Saved to your Space' + : !receiptReady + ? 'Checking saved review…' + : finished + ? 'Review ended' + : 'Ready for your review'} - {saved - ? 'Approved' - : !receiptReady - ? 'Checking' - : finished - ? 'Not saved' - : 'You decide'} + {conflict + ? 'Needs new review' + : saved + ? 'Approved' + : !receiptReady + ? 'Checking' + : finished + ? 'Not saved' + : 'You decide'}
@@ -136,6 +138,12 @@ export function PageReviewCard({ )}
+ {conflict && ( +

+ This review was saved with a different draft. Start a new review for + the changed draft. +

+ )} {error &&

{error}

} {!receiptReady && error && ( )} - {!finished && respond && receiptReady && ( + {!finished && respond && receiptReady && !conflict && ( <>