- This pull request was merged into {pr.target_branch}.
+ {isSuggestionMode ? "This suggestion was accepted into " : "This pull request was merged into "}
+ {pr.target_branch}.
@@ -252,7 +257,7 @@ export function PRActions({
open={showReopenDialog}
onOpenChange={setShowReopenDialog}
onConfirm={handleReopen}
- title="Reopen Pull Request"
+ title={isSuggestionMode ? "Reopen Suggestion" : "Reopen Pull Request"}
description={`Are you sure you want to reopen "${pr.title}"?`}
confirmLabel="Reopen"
variant="default"
@@ -341,7 +346,7 @@ export function PRActions({
Review
- {/* Merge button */}
+ {/* Merge / Accept button */}
{canMerge && (
)}
- {/* Close button */}
+ {/* Close / Reject button */}
)}
@@ -382,9 +389,11 @@ export function PRActions({
open={showMergeDialog}
onOpenChange={setShowMergeDialog}
onConfirm={handleMerge}
- title="Merge Pull Request"
- description={`Are you sure you want to merge "${pr.title}" into ${pr.target_branch}?`}
- confirmLabel="Merge"
+ title={isSuggestionMode ? "Accept Suggestion" : "Merge Pull Request"}
+ description={isSuggestionMode
+ ? `Are you sure you want to accept "${pr.title}" into ${pr.target_branch}?`
+ : `Are you sure you want to merge "${pr.title}" into ${pr.target_branch}?`}
+ confirmLabel={isSuggestionMode ? "Accept" : "Merge"}
variant="default"
>
@@ -405,16 +415,20 @@ export function PRActions({
open={showCloseDialog}
onOpenChange={setShowCloseDialog}
onConfirm={handleClose}
- title="Close Pull Request"
- description={`Are you sure you want to close "${pr.title}"? This will not delete the branch and can be reopened later.`}
- confirmLabel="Close PR"
+ title={isSuggestionMode ? "Reject Suggestion" : "Close Pull Request"}
+ description={isSuggestionMode
+ ? `Are you sure you want to reject "${pr.title}"? This will not delete the branch and can be reopened later.`
+ : `Are you sure you want to close "${pr.title}"? This will not delete the branch and can be reopened later.`}
+ confirmLabel={isSuggestionMode ? "Reject" : "Close PR"}
variant="danger"
/>
{/* Merge status */}
{!pr.can_merge && canMerge && (
- This pull request requires additional approvals before it can be merged.
+ {isSuggestionMode
+ ? "This suggestion requires additional approvals before it can be accepted."
+ : "This pull request requires additional approvals before it can be merged."}{" "}
Current: {pr.approval_count} approvals
)}
diff --git a/components/pr/PRDetail.tsx b/components/pr/PRDetail.tsx
index 828d19d6..c961fa8e 100644
--- a/components/pr/PRDetail.tsx
+++ b/components/pr/PRDetail.tsx
@@ -13,6 +13,7 @@ import { PRActions } from "./PRActions";
import { PRCommentThread } from "./PRCommentThread";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
+import type { EditorMode } from "@/lib/stores/editorModeStore";
import {
GitPullRequest,
GitMerge,
@@ -39,6 +40,7 @@ interface PRDetailProps {
accessToken?: string;
userRole?: string;
currentUserId?: string;
+ mode?: EditorMode;
className?: string;
}
@@ -48,8 +50,10 @@ export function PRDetail({
accessToken,
userRole,
currentUserId,
+ mode = "developer",
className,
}: PRDetailProps) {
+ const isSuggestionMode = mode === "standard";
const [pr, setPR] = useState
(null);
const [reviews, setReviews] = useState([]);
const [comments, setComments] = useState([]);
@@ -204,13 +208,13 @@ export function PRDetail({
case "merged":
return (
- Merged
+ {isSuggestionMode ? "Accepted" : "Merged"}
);
case "closed":
return (
- Closed
+ {isSuggestionMode ? "Rejected" : "Closed"}
);
default:
@@ -267,7 +271,7 @@ export function PRDetail({
{pr.status === "merged" && pr.merged_at
- ? `merged ${formatDate(pr.merged_at)}`
+ ? `${isSuggestionMode ? "accepted" : "merged"} ${formatDate(pr.merged_at)}`
: `opened ${formatDate(pr.created_at)}`}
{pr.github_pr_url && (
@@ -301,6 +305,7 @@ export function PRDetail({
pr={pr}
accessToken={accessToken}
userRole={userRole}
+ mode={mode}
onUpdate={setPR}
/>
)}
diff --git a/components/pr/PRList.tsx b/components/pr/PRList.tsx
index 8094e1f8..7da91c33 100644
--- a/components/pr/PRList.tsx
+++ b/components/pr/PRList.tsx
@@ -8,12 +8,20 @@ import {
} from "@/lib/api/pullRequests";
import { PRListItem } from "./PRListItem";
import { cn } from "@/lib/utils";
-import { GitPullRequest } from "lucide-react";
+import { GitPullRequest, Lightbulb } from "lucide-react";
+import type { EditorMode } from "@/lib/stores/editorModeStore";
interface PRListProps {
projectId: string;
accessToken?: string;
defaultStatus?: PRStatus | "all";
+ mode?: EditorMode;
+ /**
+ * Restrict the list to one author. Reviewers (owner/admin) leave this unset
+ * to see everyone's; everyone else passes their own id so a list titled
+ * "My Suggestions" / "My Pull Requests" actually only contains theirs.
+ */
+ authorId?: string;
className?: string;
}
@@ -21,8 +29,11 @@ export function PRList({
projectId,
accessToken,
defaultStatus = "open",
+ mode = "developer",
+ authorId,
className,
}: PRListProps) {
+ const isSuggestionMode = mode === "standard";
const [prs, setPrs] = useState([]);
const [total, setTotal] = useState(0);
const [isLoading, setIsLoading] = useState(true);
@@ -31,35 +42,53 @@ export function PRList({
const [skip, setSkip] = useState(0);
const limit = 20;
- const loadPRs = useCallback(async () => {
- if (!projectId) return;
+ // A reviewer on page 3 who switches to their own (much shorter) list would
+ // otherwise keep the stale offset and land on an empty page.
+ const [scopeKey, setScopeKey] = useState(authorId);
+ if (scopeKey !== authorId) {
+ setScopeKey(authorId);
+ setSkip(0);
+ }
- setIsLoading(true);
- setError(null);
+ const loadPRs = useCallback(
+ async (signal?: { cancelled: boolean }) => {
+ if (!projectId) return;
- try {
- const status = statusFilter === "all" ? undefined : statusFilter;
- const response = await pullRequestsApi.list(
- projectId,
- accessToken,
- status,
- undefined,
- skip,
- limit
- );
- setPrs(response.items);
- setTotal(response.total);
- } catch (err) {
- const message =
- err instanceof Error ? err.message : "Failed to load pull requests";
- setError(message);
- } finally {
- setIsLoading(false);
- }
- }, [projectId, accessToken, statusFilter, skip]);
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ const status = statusFilter === "all" ? undefined : statusFilter;
+ const response = await pullRequestsApi.list(
+ projectId,
+ accessToken,
+ status,
+ authorId,
+ skip,
+ limit
+ );
+ // A slower request for a previous scope must not overwrite the newer one.
+ if (signal?.cancelled) return;
+ setPrs(response.items);
+ setTotal(response.total);
+ } catch (err) {
+ if (signal?.cancelled) return;
+ const message =
+ err instanceof Error ? err.message : "Failed to load pull requests";
+ setError(message);
+ } finally {
+ if (!signal?.cancelled) setIsLoading(false);
+ }
+ },
+ [projectId, accessToken, statusFilter, authorId, skip]
+ );
useEffect(() => {
- loadPRs();
+ const signal = { cancelled: false };
+ loadPRs(signal);
+ return () => {
+ signal.cancelled = true;
+ };
}, [loadPRs]);
const handleStatusChange = (newStatus: PRStatus | "all") => {
@@ -67,10 +96,39 @@ export function PRList({
setSkip(0);
};
+ // The list is either scoped to the signed-in user (authorId set) or shows the
+ // whole project, so the empty state has to speak in the matching voice —
+ // "You have no…" vs "There are no…".
+ const isScopedToViewer = !!authorId;
+ const noun = isSuggestionMode ? "suggestion" : "pull request";
+ const emptyStateMessage = (() => {
+ if (statusFilter === "open") {
+ return isScopedToViewer
+ ? `You have no open ${noun}s.`
+ : `There are no open ${noun}s for this project.`;
+ }
+ if (statusFilter === "merged") {
+ const verb = isSuggestionMode ? "accepted" : "merged";
+ return isScopedToViewer
+ ? `None of your ${noun}s have been ${verb} yet.`
+ : `No ${noun}s have been ${verb} yet.`;
+ }
+ if (statusFilter === "closed") {
+ const verb = isSuggestionMode ? "rejected" : "closed";
+ return isScopedToViewer
+ ? `None of your ${noun}s have been ${verb}.`
+ : `No ${noun}s have been ${verb}.`;
+ }
+ const verb = isSuggestionMode ? "submitted" : "created";
+ return isScopedToViewer
+ ? `You have not ${verb} any ${noun}s yet.`
+ : `No ${noun}s have been ${verb} for this project.`;
+ })();
+
const statusTabs: { value: PRStatus | "all"; label: string }[] = [
{ value: "open", label: "Open" },
- { value: "merged", label: "Merged" },
- { value: "closed", label: "Closed" },
+ { value: "merged", label: isSuggestionMode ? "Accepted" : "Merged" },
+ { value: "closed", label: isSuggestionMode ? "Rejected" : "Closed" },
{ value: "all", label: "All" },
];
@@ -96,7 +154,7 @@ export function PRList({