diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx
index 0825c5cb92cc..600d0ce8efbf 100644
--- a/apps/web/src/components/CommandPalette.tsx
+++ b/apps/web/src/components/CommandPalette.tsx
@@ -175,7 +175,11 @@ import {
CommandPaletteMetaDot,
ThreadCommandSubtitle,
} from "./ThreadCommandSubtitle";
-import { ThreadRowLeadingStatus, ThreadRowTrailingStatus } from "./ThreadStatusIndicators";
+import {
+ ThreadRowLeadingStatus,
+ ThreadRowTrailingStatus,
+ ThreadSearchPullRequestNumber,
+} from "./ThreadStatusIndicators";
import { primaryServerKeybindingsAtom, primaryServerProvidersAtom } from "../state/server";
import { deriveProviderInstanceEntries, type ProviderInstanceEntry } from "../providerInstances";
import { resolveShortcutCommand, threadJumpIndexFromCommand } from "../keybindings";
@@ -1340,7 +1344,16 @@ function OpenCommandPaletteDialog(props: {
sortOrder: clientSettings.sidebarThreadSortOrder,
icon: ,
renderLeadingContent: (thread) => ,
- renderTrailingContent: (thread) => ,
+ renderTrailingContent: (thread) => (
+ <>
+
+
+ >
+ ),
renderDescription: (thread, { projectTitle }) => {
const modelInstanceId =
thread.session?.providerInstanceId ?? thread.modelSelection.instanceId;
diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx
index 90f65ea2b941..6d416456935a 100644
--- a/apps/web/src/components/Sidebar.tsx
+++ b/apps/web/src/components/Sidebar.tsx
@@ -211,6 +211,7 @@ import {
synchronizeTerminalPulse,
type TerminalStatusIndicator,
useLinkedThreadPullRequest,
+ ThreadSearchPullRequestNumber,
} from "./ThreadStatusIndicators";
import { resolveSnoozePresets, snoozeWakeLabel, type SnoozePreset } from "./Sidebar.snooze";
import { ProjectFavicon, type ProjectFaviconProject } from "./ProjectFavicon";
@@ -2019,6 +2020,7 @@ function latestTurnDiff(
}
const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
+ isSettled: boolean;
thread: SidebarThreadSummary;
project: EnvironmentProject | null;
projectDisplayName: string | null;
@@ -2118,11 +2120,12 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
tabIndex={-1}
aria-selected={props.isHighlighted}
aria-current={accessibility.current}
+ aria-describedby={`${props.resultId}-pr`}
aria-label={accessibility.label}
onMouseMove={props.onHighlight}
onClick={props.onSelect}
className={cn(
- "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",
+ "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",
props.isHighlighted || props.isRouteActive
? "bg-sidebar-row-active text-sidebar-foreground"
: "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground",
@@ -2133,12 +2136,40 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
}
>
{props.project ? (
-
+
) : null}
- {thread.title}
-
+
+ {thread.title}
+
+
+
+
+
{threadTimeLabel(thread)}
@@ -4623,6 +4654,7 @@ export default function Sidebar() {
);
return (
);
}
+
+/** Number presentation for compact thread search results. */
+function ThreadPullRequestNumber({
+ number,
+ className,
+ ...props
+}: ComponentProps<"span"> & { number: number }) {
+ return (
+
+ #{number}
+
+ );
+}
+
+function settledPrHoverColorClass(state: NonNullable["state"], isDraft = false): string {
+ switch (state) {
+ case "open":
+ if (isDraft) {
+ return "group-hover/sidebar-row:text-zinc-500 dark:group-hover/sidebar-row:text-zinc-400/80";
+ }
+ return "group-hover/sidebar-row:text-emerald-600 dark:group-hover/sidebar-row:text-emerald-300/90";
+ case "merged":
+ return "group-hover/sidebar-row:text-violet-600 dark:group-hover/sidebar-row:text-violet-300/90";
+ case "closed":
+ return "group-hover/sidebar-row:text-red-600 dark:group-hover/sidebar-row:text-red-300/90";
+ }
+}
+
+export function ThreadSearchPullRequestNumber({
+ thread,
+ query,
+ enabled = true,
+ settled = false,
+}: {
+ thread: SidebarThreadSummary;
+ query: string;
+ enabled?: boolean;
+ settled?: boolean;
+}) {
+ const normalizedQuery = query.trim().toLowerCase();
+ const matches = (candidate: Parameters[0]) =>
+ normalizedQuery.length > 0 &&
+ threadPullRequestSearchTerms(candidate).some((term) =>
+ term.toLowerCase().includes(normalizedQuery),
+ );
+ const matchedLink = visibleThreadPullRequests(thread.pullRequests).find((pr) =>
+ matches({ pullRequests: [pr] }),
+ );
+ const fallbackCandidates =
+ thread.pullRequests.length === 0
+ ? [thread.linkedPullRequest, thread.branchPullRequest]
+ : [thread.branchPullRequest];
+ const legacyReference = matchedLink
+ ? undefined
+ : fallbackCandidates.find((pr) => pr != null && matches({ linkedPullRequest: pr }));
+ const legacyStatus = useLinkedThreadPullRequest(
+ thread.environmentId,
+ legacyReference,
+ enabled,
+ undefined,
+ legacyReference,
+ );
+ const reference = matchedLink ?? legacyReference;
+ const linked = matchedLink ? linkedPullRequestSnapshotStatus(matchedLink) : legacyStatus;
+ const status = prStatusIndicator(linked?.pr ?? null, linked?.sourceControlProvider);
+ return reference ? (
+
+ ) : null;
+}
diff --git a/packages/shared/src/threadPullRequests.test.ts b/packages/shared/src/threadPullRequests.test.ts
index 4844df8f7786..a0db9e6830b0 100644
--- a/packages/shared/src/threadPullRequests.test.ts
+++ b/packages/shared/src/threadPullRequests.test.ts
@@ -433,6 +433,31 @@ describe("chain selection and badge state", () => {
});
describe("threadPullRequestSearchTerms", () => {
+ it.each([undefined, [], [link(34)]])(
+ "searches the branch PR alongside existing links (%j)",
+ (pullRequests) => {
+ const branchPullRequest = {
+ projectId: ProjectId.make("project"),
+ repository: "pingdotgg/t3code",
+ number: 11809,
+ url: "https://github.com/pingdotgg/t3code/pull/11809",
+ };
+ const terms = threadPullRequestSearchTerms({
+ pullRequests,
+ branchPullRequest,
+ linkedPullRequest: {
+ ...branchPullRequest,
+ number: 12,
+ url: "https://github.com/pingdotgg/t3code/pull/12",
+ },
+ });
+ for (const query of ["11809", "#11809", "pingdotgg/t3code#11809", branchPullRequest.url]) {
+ expect(terms.some((term) => term.includes(query))).toBe(true);
+ }
+ expect(terms).toContain(pullRequests?.length ? "#34" : "#12");
+ },
+ );
+
it("includes completed and unsynced links but excludes dismissed links", () => {
const terms = threadPullRequestSearchTerms({
pullRequests: [
diff --git a/packages/shared/src/threadPullRequests.ts b/packages/shared/src/threadPullRequests.ts
index e747c151bd1c..e58ddfce24f8 100644
--- a/packages/shared/src/threadPullRequests.ts
+++ b/packages/shared/src/threadPullRequests.ts
@@ -306,19 +306,28 @@ export function resolveThreadPullRequestBadge(
return { kind: "pull-request", others: visible.length - 1, state };
}
-/** Search terms for visible PR links, including the legacy single-link projection. */
+/** Search terms for visible PR links, the legacy projection, and the branch PR. */
export function threadPullRequestSearchTerms(thread: {
readonly pullRequests?: ReadonlyArray | undefined;
readonly linkedPullRequest?: ThreadLinkedPullRequest | null | undefined;
+ readonly branchPullRequest?: ThreadLinkedPullRequest | null | undefined;
}): string[] {
+ const branch = thread.branchPullRequest;
+ const terms = branch
+ ? [`#${branch.number}`, `${branch.repository}#${branch.number}`, branch.url]
+ : [];
if (thread.pullRequests !== undefined && thread.pullRequests.length > 0) {
- return visibleThreadPullRequests(thread.pullRequests).flatMap((link) => [
- `#${link.number}`,
- `${link.repository}#${link.number}`,
- link.url,
- link.snapshot?.title ?? "",
- ]);
+ return terms.concat(
+ visibleThreadPullRequests(thread.pullRequests).flatMap((link) => [
+ `#${link.number}`,
+ `${link.repository}#${link.number}`,
+ link.url,
+ link.snapshot?.title ?? "",
+ ]),
+ );
}
const legacy = thread.linkedPullRequest;
- return legacy ? [`#${legacy.number}`, `${legacy.repository}#${legacy.number}`, legacy.url] : [];
+ return legacy
+ ? terms.concat([`#${legacy.number}`, `${legacy.repository}#${legacy.number}`, legacy.url])
+ : terms;
}