Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,10 @@ import {
selectThreadPreviewMiniPlayer,
usePreviewMiniPlayerStore,
} from "../previewMiniPlayerStore";
import { isThreadOwnPullRequest } from "./pullRequest/pullRequestDetail.logic";
import {
isThreadOwnPullRequest,
pullRequestOwnershipCandidate,
} from "./pullRequest/pullRequestDetail.logic";
import { PullRequestDetailPanel } from "./pullRequest/PullRequestDetailPanel";
import { PullRequestDetailGhost } from "./pullRequest/PullRequestGhosts";
import { PullRequestsUnavailableState } from "./pullRequest/PullRequestsUnavailableState";
Expand Down Expand Up @@ -7074,11 +7077,14 @@ function ChatViewContent(props: ChatViewProps) {
}}
context={
isThreadOwnPullRequest(
{
projectId: linkedThreadPullRequest?.projectId ?? activeProject?.id ?? null,
repository: threadRepository,
number: activeThreadPr?.number ?? null,
},
pullRequestOwnershipCandidate({
linked: linkedThreadPullRequest,
Comment thread
t3-code[bot] marked this conversation as resolved.
inferred: {
projectId: activeProject?.id ?? null,
repository: activeProjectRepository,
number: activeThreadPr?.number ?? null,
},
}),
{
projectId: activeRightPanelSurface.projectId,
repository: activeRightPanelSurface.repository,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
pullRequestComposerTarget,
pullRequestFindingKey,
pullRequestHandoffLabels,
pullRequestOwnershipCandidate,
pullRequestReviewOutcome,
readableFailure,
shouldRefreshPullRequestActivity,
Expand Down Expand Up @@ -1186,6 +1187,20 @@ describe("how the branch stands against its base", () => {
describe("whether the panel is showing the thread's own pull request", () => {
const surface = { projectId: "proj-a", repository: "acme/app", number: 7 };

it("uses the persisted link while inferred pull request detail is unavailable", () => {
expect(
pullRequestOwnershipCandidate({
linked: { projectId: "linked-project", repository: "acme/app", number: 7 },
inferred: { projectId: "proj-a", repository: "acme/app", number: null },
}),
).toEqual({
projectId: "linked-project",
repository: "acme/app",
number: 7,
explicitlyLinked: true,
});
});

it("matches on project, repository and number together", () => {
expect(
isThreadOwnPullRequest({ projectId: "proj-a", repository: "acme/app", number: 7 }, surface),
Expand All @@ -1198,13 +1213,47 @@ describe("whether the panel is showing the thread's own pull request", () => {
).toBe(false);
});

it("keeps every agent handoff in an explicitly linked thread after its project id changes", () => {
const ownsPullRequest = isThreadOwnPullRequest(
{
projectId: "linked-project",
repository: "acme/app",
number: 7,
explicitlyLinked: true,
},
surface,
);
const composerTarget = { environmentId: "env-1", threadId: "thread-1" };

expect(ownsPullRequest).toBe(true);
expect(pullRequestComposerTarget(ownsPullRequest ? "thread" : "page", composerTarget)).toBe(
composerTarget,
);
expect(pullRequestHandoffLabels(ownsPullRequest)).toEqual({
fixFinding: "Fix in this thread",
fixCheck: "Fix in this thread",
fixFindings: "Fix findings in this thread",
});
});

it("rejects another repository or another number", () => {
expect(
isThreadOwnPullRequest({ projectId: "proj-a", repository: "acme/web", number: 7 }, surface),
).toBe(false);
expect(
isThreadOwnPullRequest({ projectId: "proj-a", repository: "acme/app", number: 8 }, surface),
).toBe(false);
expect(
isThreadOwnPullRequest(
{
projectId: "linked-project",
repository: "acme/web",
number: 7,
explicitlyLinked: true,
},
surface,
),
).toBe(false);
});

it("rejects a thread with no project or no pull request of its own", () => {
Expand Down
22 changes: 20 additions & 2 deletions apps/web/src/components/pullRequest/pullRequestDetail.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ import type {

import { inferReviewCommentFenceLanguage, type ReviewCommentContext } from "~/reviewCommentContext";

interface PullRequestOwnershipIdentity {
readonly projectId: string | null;
readonly repository: string | null;
readonly number: number | null;
}

/** Prefer the persisted link even while its live host detail is unavailable. */
export function pullRequestOwnershipCandidate(input: {
readonly linked: PullRequestOwnershipIdentity | null;
readonly inferred: PullRequestOwnershipIdentity;
}): PullRequestOwnershipIdentity & { readonly explicitlyLinked: boolean } {
return input.linked
? { ...input.linked, explicitlyLinked: true }
: { ...input.inferred, explicitlyLinked: false };
}

/** Activity changes only when the same host resource reports a newer revision. */
export function shouldRefreshPullRequestActivity(
previous: { readonly key: string; readonly updatedAt: string } | null,
Expand Down Expand Up @@ -55,6 +71,8 @@ export function isThreadOwnPullRequest(
readonly projectId: string | null;
readonly repository: string | null;
readonly number: number | null;
/** An explicit thread link owns this PR even if its project was recreated or remapped. */
readonly explicitlyLinked?: boolean;
},
surface: {
readonly projectId: string;
Expand All @@ -63,9 +81,9 @@ export function isThreadOwnPullRequest(
},
): boolean {
return (
thread.projectId === surface.projectId &&
thread.repository === surface.repository &&
thread.number === surface.number
thread.number === surface.number &&
(thread.explicitlyLinked === true || thread.projectId === surface.projectId)
);
}

Expand Down
Loading