Skip to content

Commit f1e1b4b

Browse files
committed
feat: enhance pending reviews UI with full features
Major improvements to PendingReviewsBanner: - Self-contained ConnectedPendingReviewsBanner - only needs workspaceId + chatInputAPI - Full review display with expandable diff viewer and editable comments - Pending reviews first, then completed reviews with 'show more' load - Relative timestamps (ages) for each review - updateReviewNote() method in usePendingReviews hook Styled review rendering in chat: - New shared ReviewBlock component in shared/ReviewBlock.tsx - Reviews render as styled cards in UserMessage (not raw text) - Live preview of reviews in ChatInput before sending - Custom 'review' element handler in MarkdownComponents Also removes ~10 props from AIView → PendingReviewsBanner wiring
1 parent 876f19d commit f1e1b4b

File tree

7 files changed

+579
-187
lines changed

7 files changed

+579
-187
lines changed

src/browser/components/AIView.tsx

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,8 @@ import { useSendMessageOptions } from "@/browser/hooks/useSendMessageOptions";
5151
import { useForceCompaction } from "@/browser/hooks/useForceCompaction";
5252
import { useAPI } from "@/browser/contexts/API";
5353
import { usePendingReviews } from "@/browser/hooks/usePendingReviews";
54-
import { PendingReviewsBanner } from "./PendingReviewsBanner";
54+
import { ConnectedPendingReviewsBanner } from "./PendingReviewsBanner";
5555
import type { ReviewNoteData } from "@/common/types/review";
56-
import { formatReviewNoteForChat } from "@/common/types/review";
5756

5857
interface AIViewProps {
5958
workspaceId: string;
@@ -228,11 +227,6 @@ const AIViewInner: React.FC<AIViewProps> = ({
228227
[pendingReviews]
229228
);
230229

231-
// Handler to send a review to chat input (formats to message string)
232-
const handleSendReviewToChat = useCallback((data: ReviewNoteData) => {
233-
chatInputAPI.current?.appendText(formatReviewNoteForChat(data));
234-
}, []);
235-
236230
// Handler for manual compaction from CompactionWarning click
237231
const handleCompactClick = useCallback(() => {
238232
chatInputAPI.current?.prependText("/compact\n");
@@ -622,17 +616,7 @@ const AIViewInner: React.FC<AIViewProps> = ({
622616
onCompactClick={handleCompactClick}
623617
/>
624618
)}
625-
<PendingReviewsBanner
626-
reviews={pendingReviews.reviews}
627-
pendingCount={pendingReviews.pendingCount}
628-
checkedCount={pendingReviews.checkedCount}
629-
onCheck={pendingReviews.checkReview}
630-
onUncheck={pendingReviews.uncheckReview}
631-
onSendToChat={handleSendReviewToChat}
632-
onRemove={pendingReviews.removeReview}
633-
onClearChecked={pendingReviews.clearChecked}
634-
onClearAll={pendingReviews.clearAll}
635-
/>
619+
<ConnectedPendingReviewsBanner workspaceId={workspaceId} chatInputAPI={chatInputAPI} />
636620
<ChatInput
637621
variant="workspace"
638622
workspaceId={workspaceId}

src/browser/components/ChatInput/index.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import { useTutorial } from "@/browser/contexts/TutorialContext";
7575
import { useVoiceInput } from "@/browser/hooks/useVoiceInput";
7676
import { VoiceInputButton } from "./VoiceInputButton";
7777
import { RecordingOverlay } from "./RecordingOverlay";
78+
import { ReviewBlock, hasReviewBlocks } from "../shared/ReviewBlock";
7879

7980
type TokenCountReader = () => number;
8081

@@ -1308,6 +1309,15 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
13081309
<ChatInputToast toast={toast} onDismiss={handleToastDismiss} />
13091310
)}
13101311

1312+
{/* Review preview - show styled review blocks above input */}
1313+
{variant === "workspace" && hasReviewBlocks(input) && (
1314+
<div className="border-border max-h-40 overflow-y-auto border-b px-2 pb-2">
1315+
{input.match(/<review>([\s\S]*?)<\/review>/g)?.map((match, idx) => (
1316+
<ReviewBlock key={idx} content={match.slice(8, -9)} />
1317+
))}
1318+
</div>
1319+
)}
1320+
13111321
{/* Command suggestions - workspace only */}
13121322
{variant === "workspace" && (
13131323
<CommandSuggestions

src/browser/components/Messages/MarkdownComponents.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { highlightCode } from "@/browser/utils/highlighting/highlightWorkerClien
55
import { extractShikiLines } from "@/browser/utils/highlighting/shiki-shared";
66
import { useTheme } from "@/browser/contexts/ThemeContext";
77
import { CopyButton } from "@/browser/components/ui/CopyButton";
8+
import { ReviewBlock } from "@/browser/components/shared/ReviewBlock";
89

910
interface CodeProps {
1011
node?: unknown;
@@ -111,11 +112,38 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ code, language }) => {
111112
);
112113
};
113114

115+
// Helper to extract text content from children
116+
function extractTextContent(children: ReactNode): string {
117+
if (typeof children === "string") return children;
118+
if (Array.isArray(children)) {
119+
return children
120+
.map((child): string => {
121+
if (typeof child === "string") return child;
122+
// Handle React elements - check if it's a valid element with props
123+
if (child !== null && typeof child === "object" && "props" in child) {
124+
const element = child as React.ReactElement<{ children?: ReactNode }>;
125+
if (element.props.children) {
126+
return extractTextContent(element.props.children);
127+
}
128+
}
129+
return "";
130+
})
131+
.join("");
132+
}
133+
return "";
134+
}
135+
114136
// Custom components for markdown rendering
115137
export const markdownComponents = {
116138
// Pass through pre element - let code component handle the wrapping
117139
pre: ({ children }: PreProps) => <>{children}</>,
118140

141+
// Custom review component - renders <review> tags as styled blocks
142+
review: ({ children }: { children?: ReactNode }) => {
143+
const content = extractTextContent(children);
144+
return <ReviewBlock content={content} />;
145+
},
146+
119147
// Custom anchor to open links externally
120148
a: ({ href, children }: AnchorProps) => (
121149
<a href={href} target="_blank" rel="noopener noreferrer">

src/browser/components/Messages/UserMessage.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { copyToClipboard } from "@/browser/utils/clipboard";
99
import { usePersistedState } from "@/browser/hooks/usePersistedState";
1010
import { VIM_ENABLED_KEY } from "@/common/constants/storage";
1111
import { Clipboard, ClipboardCheck, Pencil } from "lucide-react";
12+
import { ContentWithReviews, hasReviewBlocks } from "../shared/ReviewBlock";
1213

1314
interface UserMessageProps {
1415
message: DisplayedMessage & { type: "user" };
@@ -89,6 +90,9 @@ export const UserMessage: React.FC<UserMessageProps> = ({
8990
);
9091
}
9192

93+
// Check if content has review blocks
94+
const containsReviews = content && hasReviewBlocks(content);
95+
9296
// Otherwise, render as normal user message
9397
return (
9498
<MessageWindow
@@ -99,9 +103,16 @@ export const UserMessage: React.FC<UserMessageProps> = ({
99103
variant="user"
100104
>
101105
{content && (
102-
<pre className="font-primary m-0 leading-6 break-words whitespace-pre-wrap text-[var(--color-user-text)]">
103-
{content}
104-
</pre>
106+
containsReviews ? (
107+
<ContentWithReviews
108+
content={content}
109+
textClassName="font-primary m-0 leading-6 break-words whitespace-pre-wrap text-[var(--color-user-text)]"
110+
/>
111+
) : (
112+
<pre className="font-primary m-0 leading-6 break-words whitespace-pre-wrap text-[var(--color-user-text)]">
113+
{content}
114+
</pre>
115+
)
105116
)}
106117
{message.imageParts && message.imageParts.length > 0 && (
107118
<div className="mt-3 flex flex-wrap gap-3">

0 commit comments

Comments
 (0)