From ed3ffc3bc79d024467d0e32915761b9d224c2e57 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Tue, 10 Nov 2026 12:28:37 -0600 Subject: [PATCH] fix(ui): let chat review requests open the document they ask about (#20590) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - In chat, an agent can end its turn on a confirmation card that asks the person to review a saved document > - The full task view links the card's document, but the compact chat card showed the document key as plain text unless it was the plan > - A person who is asked to review a draft in chat could not open the draft from the card > - This pull request links a document on the same chat to its `#document-` panel, the same link a reply uses > - The benefit is that a review request in chat lets the person read what they are asked to approve ## Linked Issues or Issue Description **What happened?** In a chat eval run on master `f4337bb5fb` (`questions-and-approvals.reject-and-revise`), the agent saved a `draft` document and ended its turn on a confirmation card "Review research notes introduction draft" with `target: { type: "issue_document", key: "draft", revisionId }`. In the chat composer takeover, the card showed `draft` as a plain chip with no link. **Expected behavior** A confirmation card in chat opens the document it asks the person to review, like the full card in the task view does. **Steps to reproduce** Create a pending `request_confirmation` on a chat task with `payload.target = { type: "issue_document", key: "draft", revisionId }`. Open the chat. The takeover shows the `draft` chip without a link. ## What Changed - `CompactTarget` in `TaskChatCompactInteractionCard.tsx` gives an `issue_document` target on the same chat the href `#document-`. Plan targets, targets with an explicit `href`, attachments and documents on other tasks keep their current behavior. - Added a `TaskChatThread` test: a pending draft review request in the takeover links `#document-draft`. ## Verification - `npx vitest run src/components/TaskChatThread.test.tsx -t "links a review request"` in `ui`: red before the fix, green after. - `npx vitest run src/components/TaskChatThread.test.tsx src/components/coordinator/CoordinatorTaskCommitment.test.tsx src/lib/issue-thread-interactions.test.ts`: 457 passed. - `node scripts/check-token-gates.mjs`: all gates clean. - Paid chat eval on a temporary branch with this change: see the comment on this PR. ## Risks - Low risk. Plan review cards are unchanged; their plan preview already opens the plan. - No API, schema or stored state changes. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`), agentic coding with tool use in Claude Code. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- ui/src/components/TaskChatThread.test.tsx | 8 ++++++++ .../task-chat/TaskChatCompactInteractionCard.tsx | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ui/src/components/TaskChatThread.test.tsx b/ui/src/components/TaskChatThread.test.tsx index 8c8e6041e26..e7b6728c56f 100644 --- a/ui/src/components/TaskChatThread.test.tsx +++ b/ui/src/components/TaskChatThread.test.tsx @@ -4218,6 +4218,14 @@ describe.each([true, false])("Unanswered question history (conversationMode=%s)" expect(pendingIndicator()).toBeNull(); }); + it("links a review request's own document so the person can open what it asks them to review", () => { + const review = { ...planReviewInteraction(), id: "draft-review", title: "Review draft", payload: { version: 1, prompt: "Review the unpublished draft.", + target: { type: "issue_document", key: "draft", revisionId: "draft-revision-1" } } } as IssueThreadInteraction; + render(); + const link = takeover()?.querySelector('a[href="#document-draft"]'); + expect(link?.textContent).toContain("draft"); + }); + it("keeps unrelated approvals and accepted plan history when a plan changes", () => { planState.data = planDocument({ latestRevisionId: "revision-4", latestRevisionNumber: 4, updatedAt: new Date("2026-08-23T10:03:00Z") }); const review = { ...planReviewInteraction(), payload: { version: 1, prompt: "Review another task", target: { type: "issue_document", issueId: "other-task", key: "plan", revisionId: "revision-3" } } } as IssueThreadInteraction; diff --git a/ui/src/components/task-chat/TaskChatCompactInteractionCard.tsx b/ui/src/components/task-chat/TaskChatCompactInteractionCard.tsx index 818da0ad1d3..bbfc6742d85 100644 --- a/ui/src/components/task-chat/TaskChatCompactInteractionCard.tsx +++ b/ui/src/components/task-chat/TaskChatCompactInteractionCard.tsx @@ -306,7 +306,13 @@ function CompactTarget({ if (!target || !label) return null; const href = target.type === "issue_attachment" ? `/api/attachments/${encodeURIComponent(target.attachmentId)}/content` - : target.href ? normalizeRequestConfirmationTargetHref(target.href) : null; + : target.href ? normalizeRequestConfirmationTargetHref(target.href) + // A document on this chat opens in its document panel, like a reply's + // `#document-` link, so the person can read what they are asked to review. + // Plan reviews keep their header chip; their plan preview opens the plan. + : target.type === "issue_document" && target.key !== "plan" && (target.issueId ?? interaction.issueId) === interaction.issueId + ? `#document-${encodeURIComponent(target.key)}` + : null; if (href) { return (