From 1aaa9eb0c23996322fec6d4bd1e8911a2d053fcd Mon Sep 17 00:00:00 2001 From: scotttong Date: Wed, 5 Aug 2026 02:05:24 -0700 Subject: [PATCH] fix(ui): restore sticky plan confirmation CTAs Co-Authored-By: Paperclip --- .../IssuePropertiesPlansTab.tsx | 77 ++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/ui/src/components/issue-properties/IssuePropertiesPlansTab.tsx b/ui/src/components/issue-properties/IssuePropertiesPlansTab.tsx index 0e40995f689..db42607c6a8 100644 --- a/ui/src/components/issue-properties/IssuePropertiesPlansTab.tsx +++ b/ui/src/components/issue-properties/IssuePropertiesPlansTab.tsx @@ -1,10 +1,13 @@ +import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import type { Issue } from "@paperclipai/shared"; +import type { Issue, IssueThreadInteraction } from "@paperclipai/shared"; import { issuesApi } from "@/api/issues"; import { queryKeys } from "@/lib/queryKeys"; import { IssuePlanDecompositionsSection } from "@/components/IssuePlanDecompositionsSection"; import { MarkdownBody } from "@/components/MarkdownBody"; +import { DocumentAnnotationsCountChip, IssueDocumentAnnotations } from "@/components/IssueDocumentAnnotations"; import { useIssuePlanDocument } from "@/hooks/useIssuePlanDocument"; +import { useLocation } from "@/lib/router"; import { IssuePlanConfirmationActionBar } from "./IssuePlanConfirmationActionBar"; interface IssuePropertiesPlansTabProps { @@ -14,6 +17,16 @@ interface IssuePropertiesPlansTabProps { inline?: boolean; } +function hasPendingPlanConfirmation(interactions: IssueThreadInteraction[] | undefined): boolean { + return (interactions ?? []).some( + (interaction) => + interaction.kind === "request_confirmation" + && interaction.status === "pending" + && interaction.payload.target?.type === "issue_document" + && interaction.payload.target.key === "plan", + ); +} + /** * Plans tab of the redesigned properties pane (flag: enableTaskChatRedesign). * @@ -25,19 +38,42 @@ interface IssuePropertiesPlansTabProps { */ export function IssuePropertiesPlansTab({ issue, inline }: IssuePropertiesPlansTabProps) { const { data: planDocument, isLoading: planDocumentLoading } = useIssuePlanDocument(issue.id); + const location = useLocation(); + const [annotationPanelOpen, setAnnotationPanelOpen] = useState(false); const { data } = useQuery({ queryKey: queryKeys.issues.acceptedPlanDecompositions(issue.id), queryFn: () => issuesApi.listAcceptedPlanDecompositions(issue.id), }); + const { data: interactions } = useQuery({ + queryKey: queryKeys.issues.interactions(issue.id), + queryFn: () => issuesApi.listInteractions(issue.id), + }); const hasPlans = (data?.length ?? 0) > 0; + const pendingPlanConfirmation = hasPendingPlanConfirmation(interactions); if (!planDocument && !hasPlans) { return ( -
- {planDocumentLoading - ? "Loading plan…" - : "No plan yet. The plan document, accepted plans, and their revisions will appear here."} -
+ <> + {/* This is deliberately outside the plan-document gate: an interaction + can arrive before its plan document query resolves or persists. */} + +
+ {planDocumentLoading ? ( + "Loading plan…" + ) : issue.workMode === "planning" ? ( +
+

This task is in plan mode but no plan document has been written yet.

+ {pendingPlanConfirmation ? ( +

+ A plan confirmation is pending, but the plan document it should confirm is missing. +

+ ) : null} +
+ ) : ( + "No plan yet. The plan document, accepted plans, and their revisions will appear here." + )} +
+ ); } @@ -45,18 +81,41 @@ export function IssuePropertiesPlansTab({ issue, inline }: IssuePropertiesPlansT
{/* Pending plan confirmation: its CTAs pin to the pane's footer slot so they stay visible while the plan scrolls. */} - {planDocument ? : null} + {planDocument ? (
-
+
{`Revision ${planDocument.latestRevisionNumber ?? 1} · updated ${new Date(planDocument.updatedAt).toLocaleString([], { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", })}`} + setAnnotationPanelOpen((open) => !open)} + />
- {planDocument.body} + + {planDocument.body} +
) : null} {hasPlans ? (