From 1e0ac43f0ddb7d20e23c63e76876e2affe38ee66 Mon Sep 17 00:00:00 2001 From: Ammar Date: Sun, 7 Jun 2026 16:05:39 -0500 Subject: [PATCH] feat: allow collapsing attached reviews in the chat decoration Wrap the AttachedReviewsPanel in the ChatInputDecoration primitive so the reviews attached to a pending message can be collapsed/expanded like the other composer decorations. The collapsed/expanded intent persists per-workspace and defaults to expanded to preserve the prior always-visible behavior. --- .../ChatInput/AttachedReviewsPanel.tsx | 108 ++++++++++++------ src/browser/features/ChatInput/index.tsx | 1 + src/common/constants/storage.ts | 9 ++ 3 files changed, 82 insertions(+), 36 deletions(-) diff --git a/src/browser/features/ChatInput/AttachedReviewsPanel.tsx b/src/browser/features/ChatInput/AttachedReviewsPanel.tsx index ded9e817d9..7425222dc2 100644 --- a/src/browser/features/ChatInput/AttachedReviewsPanel.tsx +++ b/src/browser/features/ChatInput/AttachedReviewsPanel.tsx @@ -1,10 +1,14 @@ import React from "react"; -import { X } from "lucide-react"; +import { MessageSquare, X } from "lucide-react"; import { Tooltip, TooltipTrigger, TooltipContent } from "@/browser/components/Tooltip/Tooltip"; +import { ChatInputDecoration } from "@/browser/components/ChatPane/ChatInputDecoration"; +import { usePersistedState } from "@/browser/hooks/usePersistedState"; +import { getAttachedReviewsExpandedKey } from "@/common/constants/storage"; import { ReviewBlockFromData } from "../Shared/ReviewBlock"; import type { Review } from "@/common/types/review"; export interface AttachedReviewsPanelProps { + workspaceId: string; reviews: Review[]; onDetachAll?: () => void; onDetach?: (reviewId: string) => void; @@ -14,10 +18,17 @@ export interface AttachedReviewsPanelProps { } /** - * Displays attached reviews in the chat input area. - * Shows a header with count and "Clear all" button when multiple reviews attached. + * Displays reviews attached to the pending message as a collapsible chat-input + * decoration. Reuses the {@link ChatInputDecoration} primitive so the panel + * reads with the same collapsed chrome as the other composer decorations and so + * a long list of attachments can be tucked away without detaching them. The + * collapsed/expanded intent persists per-workspace; the panel defaults to + * expanded to preserve the prior always-visible behavior. The summary surfaces + * the count, and "Clear all" lives in the expanded body when multiple reviews + * are attached. */ export const AttachedReviewsPanel: React.FC = ({ + workspaceId, reviews, onDetachAll, onDetach, @@ -25,41 +36,66 @@ export const AttachedReviewsPanel: React.FC = ({ onDelete, onUpdateNote, }) => { + const [expanded, setExpanded] = usePersistedState( + getAttachedReviewsExpandedKey(workspaceId), + true + ); + if (reviews.length === 0) return null; return ( -
- {/* Header with count and clear all button */} -
- - {reviews.length} review{reviews.length !== 1 && "s"} attached - - {onDetachAll && reviews.length > 1 && ( - - - - - Remove all reviews from message - - )} -
- {reviews.map((review) => ( - onCheck(review.id) : undefined} - onDetach={onDetach ? () => onDetach(review.id) : undefined} - onDelete={onDelete ? () => onDelete(review.id) : undefined} - onEditComment={onUpdateNote ? (newNote) => onUpdateNote(review.id, newNote) : undefined} - /> - ))} -
+ setExpanded(!expanded)} + dataComponent="AttachedReviewsPanel" + // Unlike the decorations stacked above the composer, this panel renders + // inside the chat-input card (which already supplies the gutter), so drop + // the primitive's top border + horizontal padding and keep a bottom + // divider above the textarea, matching the panel's prior placement. + className="border-t-0 border-b px-0" + contentClassName="max-h-[50vh] space-y-2 overflow-y-auto py-1.5" + summary={ + <> + + + {reviews.length} review + {reviews.length !== 1 && "s"} attached + + + } + renderExpanded={() => ( + <> + {onDetachAll && reviews.length > 1 && ( +
+ + + + + Remove all reviews from message + +
+ )} + {reviews.map((review) => ( + onCheck(review.id) : undefined} + onDetach={onDetach ? () => onDetach(review.id) : undefined} + onDelete={onDelete ? () => onDelete(review.id) : undefined} + onEditComment={ + onUpdateNote ? (newNote) => onUpdateNote(review.id, newNote) : undefined + } + /> + ))} + + )} + /> ); }; diff --git a/src/browser/features/ChatInput/index.tsx b/src/browser/features/ChatInput/index.tsx index e0213d2075..b4062b6ffb 100644 --- a/src/browser/features/ChatInput/index.tsx +++ b/src/browser/features/ChatInput/index.tsx @@ -2992,6 +2992,7 @@ const ChatInputInner: React.FC = (props) => { {/* Hide during send to avoid duplicate display with the sent message */} {variant === "workspace" && !hideReviewsDuringSend && (