| author | |
| committer | |
| log | f90b77d80971ea4855a42abfc89ce85466e1accb |
| tree | 2eeebbb3aedcf0ec56efe898786c718e82be8bc6 |
| parent | 237d51e6cd2f935d3ddced4efe8fcd4a5d697e43 |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 87 insertions(+), 5 deletions(-)
apps/web/src/components/ChatView.tsx+18-5| ... | ... | @@ -266,7 +266,10 @@ import { |
| 266 | 266 | selectThreadPreviewMiniPlayer, |
| 267 | 267 | usePreviewMiniPlayerStore, |
| 268 | 268 | } from "../previewMiniPlayerStore"; |
| 269 | import { pullRequestPanelContext } from "./pullRequest/pullRequestDetail.logic"; | |
| 269 | import { | |
| 270 | pullRequestPanelContext, | |
| 271 | threadPullRequestPanelTarget, | |
| 272 | } from "./pullRequest/pullRequestDetail.logic"; | |
| 270 | 273 | import { PullRequestDetailPanel } from "./pullRequest/PullRequestDetailPanel"; |
| 271 | 274 | import { PullRequestDetailGhost } from "./pullRequest/PullRequestGhosts"; |
| 272 | 275 | import { PullRequestsUnavailableState } from "./pullRequest/PullRequestsUnavailableState"; |
| ... | ... | @@ -6776,12 +6779,20 @@ export default function ChatView(props: ChatViewProps) { |
| 6776 | 6779 | }, |
| 6777 | 6780 | ); |
| 6778 | 6781 | }, [activeThreadReferenceCopyTarget]); |
| 6782 | const pullRequestPanelTarget = activeThread | |
| 6783 | ? threadPullRequestPanelTarget({ | |
| 6784 | projectId: activeThread.projectId, | |
| 6785 | pullRequests: visiblePullRequests, | |
| 6786 | linkedPullRequest: linkedThreadPullRequest, | |
| 6787 | branchPullRequest: activeThreadShell?.branchPullRequest ?? activeThread.branchPullRequest, | |
| 6788 | }) | |
| 6789 | : null; | |
| 6779 | 6790 | const addPullRequestSurface = useCallback(() => { |
| 6780 | if (!supportsPullRequests || activeThreadRef === null || linkedThreadPullRequest === null) | |
| 6791 | if (!supportsPullRequests || activeThreadRef === null || pullRequestPanelTarget === null) | |
| 6781 | 6792 | return; |
| 6782 | useRightPanelStore.getState().openPullRequest(activeThreadRef, linkedThreadPullRequest); | |
| 6783 | }, [activeThreadRef, linkedThreadPullRequest, supportsPullRequests]); | |
| 6784 | const pullRequestSurfaceAvailable = supportsPullRequests && linkedThreadPullRequest !== null; | |
| 6793 | useRightPanelStore.getState().openPullRequest(activeThreadRef, pullRequestPanelTarget); | |
| 6794 | }, [activeThreadRef, pullRequestPanelTarget, supportsPullRequests]); | |
| 6795 | const pullRequestSurfaceAvailable = supportsPullRequests && pullRequestPanelTarget !== null; | |
| 6785 | 6796 | const supportsSettlement = serverConfig?.environment.capabilities.threadSettlement === true; |
| 6786 | 6797 | const supportsSnooze = serverConfig?.environment.capabilities.threadSnooze === true; |
| 6787 | 6798 | const supportsPinning = serverConfig?.environment.capabilities.threadPinning === true; |
| ... | ... | @@ -10535,6 +10546,8 @@ export default function ChatView(props: ChatViewProps) { |
| 10535 | 10546 | projectId: activeThread.projectId, |
| 10536 | 10547 | pullRequests: visiblePullRequests, |
| 10537 | 10548 | linkedPullRequest: linkedThreadPullRequest, |
| 10549 | branchPullRequest: | |
| 10550 | activeThreadShell?.branchPullRequest ?? activeThread.branchPullRequest, | |
| 10538 | 10551 | }, |
| 10539 | 10552 | renderedRightPanelSurface, |
| 10540 | 10553 | )} |
apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts+40| ... | ... | @@ -35,6 +35,7 @@ import { |
| 35 | 35 | isStackedPullRequestBase, |
| 36 | 36 | loadingPullRequestCheckoutCommand, |
| 37 | 37 | pullRequestPanelContext, |
| 38 | threadPullRequestPanelTarget, | |
| 38 | 39 | latestPullRequestReviewOutcomes, |
| 39 | 40 | newestPullRequestCommitAt, |
| 40 | 41 | mergePullRequestThreadComments, |
| ... | ... | @@ -1535,6 +1536,45 @@ describe("pull request panel context beside a thread", () => { |
| 1535 | 1536 | ); |
| 1536 | 1537 | expect(pullRequestPanelContext({ projectId: null }, surface(3))).toBe("page"); |
| 1537 | 1538 | }); |
| 1539 | ||
| 1540 | describe("the Pull request entry's target", () => { | |
| 1541 | const legacy = (number: number) => ({ | |
| 1542 | projectId: ProjectId.make("proj-a"), | |
| 1543 | repository: "pingdotgg/t3code", | |
| 1544 | number, | |
| 1545 | url: `https://github.com/pingdotgg/t3code/pull/${number}`, | |
| 1546 | }); | |
| 1547 | ||
| 1548 | it("opens a linked pull request the legacy field never named, ahead of the branch PR", () => { | |
| 1549 | expect( | |
| 1550 | threadPullRequestPanelTarget({ | |
| 1551 | projectId: "proj-a", | |
| 1552 | pullRequests: [link(15046, { source: "agent" })], | |
| 1553 | linkedPullRequest: null, | |
| 1554 | branchPullRequest: legacy(30), | |
| 1555 | }), | |
| 1556 | ).toEqual({ ...legacy(15046), host: "github.com" }); | |
| 1557 | }); | |
| 1558 | ||
| 1559 | it("reuses the legacy reference only for the same pull request on the same host", () => { | |
| 1560 | const linkedPullRequest = legacy(15046); | |
| 1561 | const onHost = (host: string) => | |
| 1562 | threadPullRequestPanelTarget({ | |
| 1563 | projectId: "proj-a", | |
| 1564 | pullRequests: [link(15046, { host, url: `https://${host}/pingdotgg/t3code/pull/15046` })], | |
| 1565 | linkedPullRequest, | |
| 1566 | }); | |
| 1567 | expect(onHost("github.com")).toBe(linkedPullRequest); | |
| 1568 | expect(onHost("github.example.com")).toMatchObject({ host: "github.example.com" }); | |
| 1569 | }); | |
| 1570 | ||
| 1571 | it("falls back to the branch pull request when the thread holds no link", () => { | |
| 1572 | expect( | |
| 1573 | threadPullRequestPanelTarget({ projectId: "proj-a", branchPullRequest: legacy(3) }), | |
| 1574 | ).toEqual(legacy(3)); | |
| 1575 | expect(threadPullRequestPanelTarget({ projectId: "proj-a", pullRequests: [] })).toBeNull(); | |
| 1576 | }); | |
| 1577 | }); | |
| 1538 | 1578 | }); |
| 1539 | 1579 | |
| 1540 | 1580 | describe("which actions need the host read again after they run", () => { |
apps/web/src/components/pullRequest/pullRequestDetail.logic.ts+29| ... | ... | @@ -27,6 +27,8 @@ import { |
| 27 | 27 | type VcsRef, |
| 28 | 28 | } from "@t3tools/contracts"; |
| 29 | 29 | import { |
| 30 | legacyThreadPullRequestKey, | |
| 31 | resolveThreadCurrentPullRequestLink, | |
| 30 | 32 | threadPullRequestKeysEqual, |
| 31 | 33 | visibleThreadPullRequests, |
| 32 | 34 | } from "@t3tools/shared/threadPullRequests"; |
| ... | ... | @@ -237,6 +239,33 @@ export function pullRequestPanelContext( |
| 237 | 239 | : "page"; |
| 238 | 240 | } |
| 239 | 241 | |
| 242 | export function threadPullRequestPanelTarget(thread: { | |
| 243 | readonly projectId: string; | |
| 244 | readonly pullRequests?: ReadonlyArray<ThreadPullRequestLink> | undefined; | |
| 245 | readonly linkedPullRequest?: ThreadLinkedPullRequest | null | undefined; | |
| 246 | readonly branchPullRequest?: ThreadLinkedPullRequest | null | undefined; | |
| 247 | }) { | |
| 248 | const current = resolveThreadCurrentPullRequestLink(thread.pullRequests ?? []); | |
| 249 | const legacy = thread.linkedPullRequest; | |
| 250 | if ( | |
| 251 | current !== null && | |
| 252 | legacy != null && | |
| 253 | threadPullRequestKeysEqual(current, legacyThreadPullRequestKey(legacy)) | |
| 254 | ) { | |
| 255 | return legacy; | |
| 256 | } | |
| 257 | if (current !== null) { | |
| 258 | return { | |
| 259 | projectId: thread.projectId, | |
| 260 | host: current.host, | |
| 261 | repository: current.repository, | |
| 262 | number: current.number, | |
| 263 | url: current.url, | |
| 264 | }; | |
| 265 | } | |
| 266 | return legacy ?? thread.branchPullRequest ?? null; | |
| 267 | } | |
| 268 | ||
| 240 | 269 | /** Names where a pull-request task will land, without letting each surface guess independently. */ |
| 241 | 270 | export function pullRequestHandoffLabels(inThisThread: boolean) { |
| 242 | 271 | return inThisThread |