| author | |
| committer | |
| log | a908a7ac8780c5b4d6fb0bed8945d640f69d2d1d |
| tree | 367babe6c013299f964edb766deecc55f8533bad |
| parent | 6cf05264257093476a41f73439b1fa745427a007 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: maria-rcks <maria@kuuro.net>7 files changed, 216 insertions(+), 16 deletions(-)
apps/web/src/components/CommandPalette.tsx+16-2| ... | @@ -211,7 +211,11 @@ import { | ... | @@ -211,7 +211,11 @@ import { |
| 211 | CommandPaletteMetaDot, | 211 | CommandPaletteMetaDot, |
| 212 | ThreadCommandSubtitle, | 212 | ThreadCommandSubtitle, |
| 213 | } from "./ThreadCommandSubtitle"; | 213 | } from "./ThreadCommandSubtitle"; |
| 214 | import { ThreadRowLeadingStatus, ThreadRowTrailingStatus } from "./ThreadStatusIndicators"; | 214 | import { |
| 215 | ThreadRowLeadingStatus, | ||
| 216 | ThreadRowTrailingStatus, | ||
| 217 | ThreadSearchPullRequestNumber, | ||
| 218 | } from "./ThreadStatusIndicators"; | ||
| 215 | import { primaryServerKeybindingsAtom, primaryServerProvidersAtom } from "../state/server"; | 219 | import { primaryServerKeybindingsAtom, primaryServerProvidersAtom } from "../state/server"; |
| 216 | import { | 220 | import { |
| 217 | applyProviderInstanceSettings, | 221 | applyProviderInstanceSettings, |
| ... | @@ -1323,7 +1327,16 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1323,7 +1327,16 @@ function OpenCommandPaletteDialog(props: { |
| 1323 | sortOrder: clientSettings.sidebarThreadSortOrder, | 1327 | sortOrder: clientSettings.sidebarThreadSortOrder, |
| 1324 | icon: <MessageSquareIcon className={ITEM_ICON_CLASS} />, | 1328 | icon: <MessageSquareIcon className={ITEM_ICON_CLASS} />, |
| 1325 | renderLeadingContent: (thread) => <ThreadRowLeadingStatus thread={thread} />, | 1329 | renderLeadingContent: (thread) => <ThreadRowLeadingStatus thread={thread} />, |
| 1326 | renderTrailingContent: (thread) => <ThreadRowTrailingStatus thread={thread} />, | 1330 | renderTrailingContent: (thread) => ( |
| 1331 | <> | ||
| 1332 | <ThreadSearchPullRequestNumber | ||
| 1333 | thread={thread} | ||
| 1334 | query={threadSearchQuery} | ||
| 1335 | settled={thread.settledOverride === "settled"} | ||
| 1336 | /> | ||
| 1337 | <ThreadRowTrailingStatus thread={thread} /> | ||
| 1338 | </> | ||
| 1339 | ), | ||
| 1327 | renderDescription: (thread, { projectTitle }) => { | 1340 | renderDescription: (thread, { projectTitle }) => { |
| 1328 | const modelInstanceId = | 1341 | const modelInstanceId = |
| 1329 | thread.runtime?.providerInstanceId ?? thread.modelSelection.instanceId; | 1342 | thread.runtime?.providerInstanceId ?? thread.modelSelection.instanceId; |
| ... | @@ -1383,6 +1396,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1383,6 +1396,7 @@ function OpenCommandPaletteDialog(props: { |
| 1383 | providerEntryByEnvironmentAndInstanceId, | 1396 | providerEntryByEnvironmentAndInstanceId, |
| 1384 | threadContentMatchByKey, | 1397 | threadContentMatchByKey, |
| 1385 | threadSearch.query, | 1398 | threadSearch.query, |
| 1399 | threadSearchQuery, | ||
| 1386 | threads, | 1400 | threads, |
| 1387 | ], | 1401 | ], |
| 1388 | ); | 1402 | ); |
apps/web/src/components/Sidebar.logic.test.ts+22| ... | @@ -40,6 +40,7 @@ import { | ... | @@ -40,6 +40,7 @@ import { |
| 40 | resolveThreadRowClassName, | 40 | resolveThreadRowClassName, |
| 41 | resolveThreadStatusPill, | 41 | resolveThreadStatusPill, |
| 42 | resolveWorkingStartedAt, | 42 | resolveWorkingStartedAt, |
| 43 | pullRequestMatchesSearchQuery, | ||
| 43 | searchSidebarThreads, | 44 | searchSidebarThreads, |
| 44 | shouldClearThreadSelectionOnMouseDown, | 45 | shouldClearThreadSelectionOnMouseDown, |
| 45 | shouldShowSidebarV2Duration, | 46 | shouldShowSidebarV2Duration, |
| ... | @@ -994,6 +995,27 @@ describe("resolveSidebarThreadStatus", () => { | ... | @@ -994,6 +995,27 @@ describe("resolveSidebarThreadStatus", () => { |
| 994 | }); | 995 | }); |
| 995 | }); | 996 | }); |
| 996 | 997 | ||
| 998 | describe("pullRequestMatchesSearchQuery", () => { | ||
| 999 | const branchPullRequest = { | ||
| 1000 | projectId: ProjectId.make("project"), | ||
| 1001 | repository: "pingdotgg/t3code", | ||
| 1002 | number: 123, | ||
| 1003 | url: "https://github.com/pingdotgg/t3code/pull/123", | ||
| 1004 | }; | ||
| 1005 | |||
| 1006 | it("matches when every query token appears in the PR's terms", () => { | ||
| 1007 | expect(pullRequestMatchesSearchQuery({ branchPullRequest }, "123")).toBe(true); | ||
| 1008 | expect(pullRequestMatchesSearchQuery({ branchPullRequest }, "pingdotgg/t3code #123")).toBe( | ||
| 1009 | true, | ||
| 1010 | ); | ||
| 1011 | }); | ||
| 1012 | |||
| 1013 | it("rejects queries with a token the PR does not carry, and blank queries", () => { | ||
| 1014 | expect(pullRequestMatchesSearchQuery({ branchPullRequest }, "rebase 123")).toBe(false); | ||
| 1015 | expect(pullRequestMatchesSearchQuery({ branchPullRequest }, " ")).toBe(false); | ||
| 1016 | }); | ||
| 1017 | }); | ||
| 1018 | |||
| 997 | describe("searchSidebarThreads", () => { | 1019 | describe("searchSidebarThreads", () => { |
| 998 | const searchThread = (id: string, title: string, project: string) => ({ | 1020 | const searchThread = (id: string, title: string, project: string) => ({ |
| 999 | environmentId: localEnvironmentId, | 1021 | environmentId: localEnvironmentId, |
apps/web/src/components/Sidebar.logic.ts+16-1| ... | @@ -23,7 +23,7 @@ import { | ... | @@ -23,7 +23,7 @@ import { |
| 23 | type ThreadSortInput, | 23 | type ThreadSortInput, |
| 24 | } from "../lib/threadSort"; | 24 | } from "../lib/threadSort"; |
| 25 | import type { SidebarThreadSummary, Thread } from "../types"; | 25 | import type { SidebarThreadSummary, Thread } from "../types"; |
| 26 | import { cn } from "../lib/utils"; | 26 | import { cn, normalizeSearchText } from "../lib/utils"; |
| 27 | import { isLatestRunSettled } from "../session-logic"; | 27 | import { isLatestRunSettled } from "../session-logic"; |
| 28 | import { resolveServerBackedAppStageLabel } from "../branding.logic"; | 28 | import { resolveServerBackedAppStageLabel } from "../branding.logic"; |
| 29 | 29 | ||
| ... | @@ -1110,6 +1110,21 @@ export function searchSidebarThreads< | ... | @@ -1110,6 +1110,21 @@ export function searchSidebarThreads< |
| 1110 | return [...titleMatches, ...contentMatches]; | 1110 | return [...titleMatches, ...contentMatches]; |
| 1111 | } | 1111 | } |
| 1112 | 1112 | ||
| 1113 | /** | ||
| 1114 | * Whether a PR's search terms contain every whitespace-separated token of the | ||
| 1115 | * query. Search results use this to pick the PR number to show, so it has to | ||
| 1116 | * accept the multi-token queries the command palette matches on. | ||
| 1117 | */ | ||
| 1118 | export function pullRequestMatchesSearchQuery( | ||
| 1119 | pullRequest: Parameters<typeof threadPullRequestSearchTerms>[0], | ||
| 1120 | query: string, | ||
| 1121 | ): boolean { | ||
| 1122 | const normalizedQuery = normalizeSearchText(query); | ||
| 1123 | if (normalizedQuery.length === 0) return false; | ||
| 1124 | const haystack = normalizeSearchText(threadPullRequestSearchTerms(pullRequest).join(" ")); | ||
| 1125 | return normalizedQuery.split(" ").every((token) => haystack.includes(token)); | ||
| 1126 | } | ||
| 1127 | |||
| 1113 | export function filterSidebarProjectScopeItems<TItem extends { readonly value: string }>(input: { | 1128 | export function filterSidebarProjectScopeItems<TItem extends { readonly value: string }>(input: { |
| 1114 | items: readonly TItem[]; | 1129 | items: readonly TItem[]; |
| 1115 | query: string; | 1130 | query: string; |
apps/web/src/components/Sidebar.tsx+36-4| ... | @@ -248,6 +248,7 @@ import { | ... | @@ -248,6 +248,7 @@ import { |
| 248 | synchronizeTerminalPulse, | 248 | synchronizeTerminalPulse, |
| 249 | type TerminalStatusIndicator, | 249 | type TerminalStatusIndicator, |
| 250 | useLinkedThreadPullRequest, | 250 | useLinkedThreadPullRequest, |
| 251 | ThreadSearchPullRequestNumber, | ||
| 251 | } from "./ThreadStatusIndicators"; | 252 | } from "./ThreadStatusIndicators"; |
| 252 | import { resolveSnoozePresets, snoozeWakeLabel, type SnoozePreset } from "./Sidebar.snooze"; | 253 | import { resolveSnoozePresets, snoozeWakeLabel, type SnoozePreset } from "./Sidebar.snooze"; |
| 253 | import { ProjectFavicon, type ProjectFaviconProject } from "./ProjectFavicon"; | 254 | import { ProjectFavicon, type ProjectFaviconProject } from "./ProjectFavicon"; |
| ... | @@ -2233,6 +2234,7 @@ function latestRunDiff( | ... | @@ -2233,6 +2234,7 @@ function latestRunDiff( |
| 2233 | } | 2234 | } |
| 2234 | 2235 | ||
| 2235 | const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { | 2236 | const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { |
| 2237 | isSettled: boolean; | ||
| 2236 | thread: SidebarThreadSummary; | 2238 | thread: SidebarThreadSummary; |
| 2237 | project: EnvironmentProject | null; | 2239 | project: EnvironmentProject | null; |
| 2238 | projectDisplayName: string | null; | 2240 | projectDisplayName: string | null; |
| ... | @@ -2332,11 +2334,12 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { | ... | @@ -2332,11 +2334,12 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { |
| 2332 | tabIndex={-1} | 2334 | tabIndex={-1} |
| 2333 | aria-selected={props.isHighlighted} | 2335 | aria-selected={props.isHighlighted} |
| 2334 | aria-current={accessibility.current} | 2336 | aria-current={accessibility.current} |
| 2337 | aria-describedby={`${props.resultId}-pr`} | ||
| 2335 | aria-label={accessibility.label} | 2338 | aria-label={accessibility.label} |
| 2336 | onMouseMove={props.onHighlight} | 2339 | onMouseMove={props.onHighlight} |
| 2337 | onClick={props.onSelect} | 2340 | onClick={props.onSelect} |
| 2338 | className={cn( | 2341 | className={cn( |
| 2339 | "flex min-h-9 w-full cursor-pointer items-center gap-2.5 rounded-md px-2.5 py-1 text-left text-sm outline-none", | 2342 | "group/sidebar-row group/v2-row flex min-h-9 w-full cursor-pointer items-center gap-2.5 rounded-md px-2.5 py-1 text-left text-sm outline-none", |
| 2340 | props.isHighlighted || props.isRouteActive | 2343 | props.isHighlighted || props.isRouteActive |
| 2341 | ? "bg-sidebar-row-active text-sidebar-foreground" | 2344 | ? "bg-sidebar-row-active text-sidebar-foreground" |
| 2342 | : "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground", | 2345 | : "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground", |
| ... | @@ -2347,12 +2350,40 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { | ... | @@ -2347,12 +2350,40 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: { |
| 2347 | } | 2350 | } |
| 2348 | > | 2351 | > |
| 2349 | {props.project ? ( | 2352 | {props.project ? ( |
| 2350 | <ProjectFavicon project={props.project} className="size-4 shrink-0" /> | 2353 | <ProjectFavicon |
| 2354 | project={props.project} | ||
| 2355 | className={cn( | ||
| 2356 | "size-4 shrink-0 transition-opacity", | ||
| 2357 | props.isSettled && | ||
| 2358 | !props.isRouteActive && | ||
| 2359 | !props.isHighlighted && | ||
| 2360 | "opacity-40 grayscale group-hover/sidebar-row:opacity-100 group-hover/sidebar-row:grayscale-0", | ||
| 2361 | )} | ||
| 2362 | /> | ||
| 2351 | ) : null} | 2363 | ) : null} |
| 2352 | <span className="flex min-w-0 flex-1 flex-col"> | 2364 | <span className="flex min-w-0 flex-1 flex-col"> |
| 2353 | <span className="flex min-w-0 items-center gap-2.5"> | 2365 | <span className="flex min-w-0 items-center gap-2.5"> |
| 2354 | <span className="min-w-0 flex-1 truncate">{thread.title}</span> | 2366 | <span |
| 2355 | <span className="shrink-0 text-xs text-muted-foreground/55 tabular-nums"> | 2367 | className={cn( |
| 2368 | "min-w-0 flex-1 truncate", | ||
| 2369 | props.isSettled | ||
| 2370 | ? "font-normal text-secondary-label/70 group-hover/sidebar-row:text-foreground" | ||
| 2371 | : "font-medium text-foreground", | ||
| 2372 | (props.isHighlighted || props.isRouteActive) && "text-foreground", | ||
| 2373 | thread.titleRegeneration != null && "opacity-55", | ||
| 2374 | )} | ||
| 2375 | > | ||
| 2376 | {thread.title} | ||
| 2377 | </span> | ||
| 2378 | <span id={`${props.resultId}-pr`} className="contents"> | ||
| 2379 | <ThreadSearchPullRequestNumber | ||
| 2380 | thread={thread} | ||
| 2381 | query={props.searchQuery} | ||
| 2382 | enabled={leaseLiveStatus} | ||
| 2383 | settled={props.isSettled} | ||
| 2384 | /> | ||
| 2385 | </span> | ||
| 2386 | <span className="w-8 shrink-0 text-right text-xs text-muted-foreground/55 tabular-nums"> | ||
| 2356 | {threadTimeLabel(thread)} | 2387 | {threadTimeLabel(thread)} |
| 2357 | </span> | 2388 | </span> |
| 2358 | </span> | 2389 | </span> |
| ... | @@ -5167,6 +5198,7 @@ export default function Sidebar() { | ... | @@ -5167,6 +5198,7 @@ export default function Sidebar() { |
| 5167 | ); | 5198 | ); |
| 5168 | return ( | 5199 | return ( |
| 5169 | <SidebarSearchResultRow | 5200 | <SidebarSearchResultRow |
| 5201 | isSettled={settledThreadKeys.has(threadKey)} | ||
| 5170 | key={threadKey} | 5202 | key={threadKey} |
| 5171 | thread={thread} | 5203 | thread={thread} |
| 5172 | project={ | 5204 | project={ |
apps/web/src/components/ThreadStatusIndicators.tsx+84-1| ... | @@ -27,7 +27,13 @@ import { | ... | @@ -27,7 +27,13 @@ import { |
| 27 | type ThreadPullRequestBadge, | 27 | type ThreadPullRequestBadge, |
| 28 | } from "@t3tools/shared/threadPullRequests"; | 28 | } from "@t3tools/shared/threadPullRequests"; |
| 29 | import { useRender } from "@base-ui/react/use-render"; | 29 | import { useRender } from "@base-ui/react/use-render"; |
| 30 | import { type ReactNode, type AnimationEvent, type MouseEvent, type ReactElement } from "react"; | 30 | import { |
| 31 | type ReactNode, | ||
| 32 | type AnimationEvent, | ||
| 33 | type MouseEvent, | ||
| 34 | type ReactElement, | ||
| 35 | type ComponentProps, | ||
| 36 | } from "react"; | ||
| 31 | import { cn } from "../lib/utils"; | 37 | import { cn } from "../lib/utils"; |
| 32 | 38 | ||
| 33 | import { parseChangeRequestUrl } from "../lib/openPullRequestLink"; | 39 | import { parseChangeRequestUrl } from "../lib/openPullRequestLink"; |
| ... | @@ -38,6 +44,7 @@ import { vcsEnvironment } from "../state/vcs"; | ... | @@ -38,6 +44,7 @@ import { vcsEnvironment } from "../state/vcs"; |
| 38 | import { useUiStateStore } from "../uiStateStore"; | 44 | import { useUiStateStore } from "../uiStateStore"; |
| 39 | import { sourceControlClients } from "@t3tools/client-runtime/source-control-clients"; | 45 | import { sourceControlClients } from "@t3tools/client-runtime/source-control-clients"; |
| 40 | import { | 46 | import { |
| 47 | pullRequestMatchesSearchQuery, | ||
| 41 | resolveThreadLastVisitedAt, | 48 | resolveThreadLastVisitedAt, |
| 42 | resolveThreadStatusPill, | 49 | resolveThreadStatusPill, |
| 43 | type ThreadStatusPill, | 50 | type ThreadStatusPill, |
| ... | @@ -1037,3 +1044,79 @@ export function ThreadRowTrailingStatus({ thread }: { thread: SidebarThreadSumma | ... | @@ -1037,3 +1044,79 @@ export function ThreadRowTrailingStatus({ thread }: { thread: SidebarThreadSumma |
| 1037 | </span> | 1044 | </span> |
| 1038 | ); | 1045 | ); |
| 1039 | } | 1046 | } |
| 1047 | |||
| 1048 | /** Number presentation for compact thread search results. */ | ||
| 1049 | function ThreadPullRequestNumber({ | ||
| 1050 | number, | ||
| 1051 | className, | ||
| 1052 | ...props | ||
| 1053 | }: ComponentProps<"span"> & { number: number }) { | ||
| 1054 | return ( | ||
| 1055 | <span {...props} className={cn("shrink-0 text-xs tabular-nums", className)}> | ||
| 1056 | #{number} | ||
| 1057 | </span> | ||
| 1058 | ); | ||
| 1059 | } | ||
| 1060 | |||
| 1061 | function settledPrHoverColorClass(state: NonNullable<ThreadPr>["state"], isDraft = false): string { | ||
| 1062 | switch (state) { | ||
| 1063 | case "open": | ||
| 1064 | if (isDraft) { | ||
| 1065 | return "group-hover/sidebar-row:text-zinc-500 dark:group-hover/sidebar-row:text-zinc-400/80"; | ||
| 1066 | } | ||
| 1067 | return "group-hover/sidebar-row:text-emerald-600 dark:group-hover/sidebar-row:text-emerald-300/90"; | ||
| 1068 | case "merged": | ||
| 1069 | return "group-hover/sidebar-row:text-violet-600 dark:group-hover/sidebar-row:text-violet-300/90"; | ||
| 1070 | case "closed": | ||
| 1071 | return "group-hover/sidebar-row:text-red-600 dark:group-hover/sidebar-row:text-red-300/90"; | ||
| 1072 | } | ||
| 1073 | } | ||
| 1074 | |||
| 1075 | export function ThreadSearchPullRequestNumber({ | ||
| 1076 | thread, | ||
| 1077 | query, | ||
| 1078 | enabled = true, | ||
| 1079 | settled = false, | ||
| 1080 | }: { | ||
| 1081 | thread: SidebarThreadSummary; | ||
| 1082 | query: string; | ||
| 1083 | enabled?: boolean; | ||
| 1084 | settled?: boolean; | ||
| 1085 | }) { | ||
| 1086 | const matchedLink = visibleThreadPullRequests(thread.pullRequests).find((pr) => | ||
| 1087 | pullRequestMatchesSearchQuery({ pullRequests: [pr] }, query), | ||
| 1088 | ); | ||
| 1089 | const fallbackCandidates = | ||
| 1090 | thread.pullRequests.length === 0 | ||
| 1091 | ? [thread.linkedPullRequest, thread.branchPullRequest] | ||
| 1092 | : [thread.branchPullRequest]; | ||
| 1093 | const legacyReference = matchedLink | ||
| 1094 | ? undefined | ||
| 1095 | : fallbackCandidates.find( | ||
| 1096 | (pr) => pr != null && pullRequestMatchesSearchQuery({ linkedPullRequest: pr }, query), | ||
| 1097 | ); | ||
| 1098 | const legacyStatus = useLinkedThreadPullRequest( | ||
| 1099 | thread.environmentId, | ||
| 1100 | legacyReference, | ||
| 1101 | enabled, | ||
| 1102 | undefined, | ||
| 1103 | legacyReference, | ||
| 1104 | ); | ||
| 1105 | const reference = matchedLink ?? legacyReference; | ||
| 1106 | const linked = matchedLink ? linkedPullRequestSnapshotStatus(matchedLink) : legacyStatus; | ||
| 1107 | const status = prStatusIndicator(linked?.pr ?? null, linked?.sourceControlProvider); | ||
| 1108 | return reference ? ( | ||
| 1109 | <ThreadPullRequestNumber | ||
| 1110 | number={reference.number} | ||
| 1111 | className={ | ||
| 1112 | settled | ||
| 1113 | ? cn( | ||
| 1114 | "text-secondary-label transition-colors", | ||
| 1115 | linked?.pr && settledPrHoverColorClass(linked.pr.state, linked.pr.isDraft), | ||
| 1116 | ) | ||
| 1117 | : (status?.colorClass ?? "text-secondary-label") | ||
| 1118 | } | ||
| 1119 | aria-label={status?.tooltip ?? `Pull request #${reference.number}`} | ||
| 1120 | /> | ||
| 1121 | ) : null; | ||
| 1122 | } |
packages/shared/src/threadPullRequests.test.ts+25| ... | @@ -445,6 +445,31 @@ describe("chain selection and badge state", () => { | ... | @@ -445,6 +445,31 @@ describe("chain selection and badge state", () => { |
| 445 | }); | 445 | }); |
| 446 | 446 | ||
| 447 | describe("threadPullRequestSearchTerms", () => { | 447 | describe("threadPullRequestSearchTerms", () => { |
| 448 | it.each([undefined, [], [link(34)]])( | ||
| 449 | "searches the branch PR alongside existing links (%j)", | ||
| 450 | (pullRequests) => { | ||
| 451 | const branchPullRequest = { | ||
| 452 | projectId: ProjectId.make("project"), | ||
| 453 | repository: "pingdotgg/t3code", | ||
| 454 | number: 11809, | ||
| 455 | url: "https://github.com/pingdotgg/t3code/pull/11809", | ||
| 456 | }; | ||
| 457 | const terms = threadPullRequestSearchTerms({ | ||
| 458 | pullRequests, | ||
| 459 | branchPullRequest, | ||
| 460 | linkedPullRequest: { | ||
| 461 | ...branchPullRequest, | ||
| 462 | number: 12, | ||
| 463 | url: "https://github.com/pingdotgg/t3code/pull/12", | ||
| 464 | }, | ||
| 465 | }); | ||
| 466 | for (const query of ["11809", "#11809", "pingdotgg/t3code#11809", branchPullRequest.url]) { | ||
| 467 | expect(terms.some((term) => term.includes(query))).toBe(true); | ||
| 468 | } | ||
| 469 | expect(terms).toContain(pullRequests?.length ? "#34" : "#12"); | ||
| 470 | }, | ||
| 471 | ); | ||
| 472 | |||
| 448 | it("includes completed and unsynced links but excludes dismissed links", () => { | 473 | it("includes completed and unsynced links but excludes dismissed links", () => { |
| 449 | const terms = threadPullRequestSearchTerms({ | 474 | const terms = threadPullRequestSearchTerms({ |
| 450 | pullRequests: [ | 475 | pullRequests: [ |
packages/shared/src/threadPullRequests.ts+17-8| ... | @@ -308,21 +308,30 @@ export function resolveThreadPullRequestBadge( | ... | @@ -308,21 +308,30 @@ export function resolveThreadPullRequestBadge( |
| 308 | return { kind: "pull-request", others: visible.length - 1, state }; | 308 | return { kind: "pull-request", others: visible.length - 1, state }; |
| 309 | } | 309 | } |
| 310 | 310 | ||
| 311 | /** Search terms for visible PR links, including the legacy single-link projection. */ | 311 | /** Search terms for visible PR links, the legacy projection, and the branch PR. */ |
| 312 | export function threadPullRequestSearchTerms(thread: { | 312 | export function threadPullRequestSearchTerms(thread: { |
| 313 | readonly pullRequests?: ReadonlyArray<ThreadPullRequestLink> | undefined; | 313 | readonly pullRequests?: ReadonlyArray<ThreadPullRequestLink> | undefined; |
| 314 | readonly linkedPullRequest?: ThreadLinkedPullRequest | null | undefined; | 314 | readonly linkedPullRequest?: ThreadLinkedPullRequest | null | undefined; |
| 315 | readonly branchPullRequest?: ThreadLinkedPullRequest | null | undefined; | ||
| 315 | }): string[] { | 316 | }): string[] { |
| 317 | const branch = thread.branchPullRequest; | ||
| 318 | const terms = branch | ||
| 319 | ? [`#${branch.number}`, `${branch.repository}#${branch.number}`, branch.url] | ||
| 320 | : []; | ||
| 316 | if (thread.pullRequests !== undefined && thread.pullRequests.length > 0) { | 321 | if (thread.pullRequests !== undefined && thread.pullRequests.length > 0) { |
| 317 | return visibleThreadPullRequests(thread.pullRequests).flatMap((link) => [ | 322 | return terms.concat( |
| 318 | `#${link.number}`, | 323 | visibleThreadPullRequests(thread.pullRequests).flatMap((link) => [ |
| 319 | `${link.repository}#${link.number}`, | 324 | `#${link.number}`, |
| 320 | link.url, | 325 | `${link.repository}#${link.number}`, |
| 321 | link.snapshot?.title ?? "", | 326 | link.url, |
| 322 | ]); | 327 | link.snapshot?.title ?? "", |
| 328 | ]), | ||
| 329 | ); | ||
| 323 | } | 330 | } |
| 324 | const legacy = thread.linkedPullRequest; | 331 | const legacy = thread.linkedPullRequest; |
| 325 | return legacy ? [`#${legacy.number}`, `${legacy.repository}#${legacy.number}`, legacy.url] : []; | 332 | return legacy |
| 333 | ? terms.concat([`#${legacy.number}`, `${legacy.repository}#${legacy.number}`, legacy.url]) | ||
| 334 | : terms; | ||
| 326 | } | 335 | } |
| 327 | 336 | ||
| 328 | /** Older V2 event payloads stored one link; an explicit empty array means it was unlinked. */ | 337 | /** Older V2 event payloads stored one link; an explicit empty array means it was unlinked. */ |