diff --git a/apps/web/src/components/pullRequest/PullRequestsPage.tsx b/apps/web/src/components/pullRequest/PullRequestsPage.tsx index 4c72a9cd6ebe..26e672ebbfb5 100644 --- a/apps/web/src/components/pullRequest/PullRequestsPage.tsx +++ b/apps/web/src/components/pullRequest/PullRequestsPage.tsx @@ -44,6 +44,7 @@ import { pullRequestOverrideAfterAction, reusePullRequestEntries, settlePullRequestOverrides, + comparePullRequestsByRecency, largestPullRequestReadCount, } from "./pullRequestList.logic"; import { @@ -560,7 +561,7 @@ export function PullRequestsPage() { const held = new Set(ordered.entries.map(pullRequestEntryKey)); const arrived = answered.entries.filter((entry) => !held.has(pullRequestEntryKey(entry))); const appended = rankPullRequestMatches( - arrived.toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt)), + arrived.toSorted(comparePullRequestsByRecency), sentParsed.text, ); setOrdered({ key: filterKey, source: answered, entries: [...ordered.entries, ...appended] }); diff --git a/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts b/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts index ad7e99ecf87e..f00757b37dea 100644 --- a/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts +++ b/apps/web/src/components/pullRequest/pullRequestDetail.logic.test.ts @@ -313,6 +313,19 @@ describe("review verdicts", () => { }); describe("pull request timeline", () => { + it("orders host timestamps by instant across offsets and fractional seconds", () => { + const events = buildPullRequestTimeline({ + ...TIMELINE_SOURCE, + createdAt: "2026-07-05T01:00:00+02:00", + commits: [{ oid: "commit", messageHeadline: "", committedDate: "2026-07-05T00:30:00Z" }], + comments: [{ ...TIMELINE_SOURCE.comments[0]!, createdAt: "2026-07-05T00:30:00.5Z" }], + mergedAt: null, + closedAt: null, + }); + + expect(events.map((event) => event.id)).toEqual(["c1", "commit", "created"]); + }); + it("drops a body that is nothing but a bot's HTML comment, and keeps one that says more", () => { const events = buildPullRequestTimeline({ ...TIMELINE_SOURCE, diff --git a/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts b/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts index b4d7a7052163..3146fe39fab1 100644 --- a/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts +++ b/apps/web/src/components/pullRequest/pullRequestDetail.logic.ts @@ -762,7 +762,7 @@ export function buildPullRequestTimeline( }, ] : []), - ].toSorted((left, right) => right.at.localeCompare(left.at)); + ].toSorted((left, right) => (instant(right.at) || 0) - (instant(left.at) || 0)); } const FINDING_LIMIT = 20; diff --git a/apps/web/src/components/pullRequest/pullRequestList.logic.test.ts b/apps/web/src/components/pullRequest/pullRequestList.logic.test.ts index 7bbc0c3ab0a6..6685095b5774 100644 --- a/apps/web/src/components/pullRequest/pullRequestList.logic.test.ts +++ b/apps/web/src/components/pullRequest/pullRequestList.logic.test.ts @@ -468,6 +468,20 @@ describe("merging the environments' own listings", () => { ...overrides, }) as Parameters[0][number][1]; + it("orders host timestamps by instant across offsets and fractional seconds", () => { + const entries = [ + entry({ number: 1, updatedAt: "2026-07-05T01:00:00+02:00" }), + entry({ number: 2, updatedAt: "2026-07-05T00:30:00Z" }), + entry({ number: 3, updatedAt: "2026-07-05T00:30:00.5Z" }), + ]; + const merged = mergePullRequestLists([ + [ENV_1, answer({ entries: entries.slice(0, 2) })], + [ENV_2, answer({ entries: entries.slice(2) })], + ]); + + expect(merged?.entries.map((row) => row.number)).toEqual([3, 2, 1]); + }); + it("tags every row with the environment that read it, newest first", () => { const merged = mergePullRequestLists([ [ENV_1, answer({ entries: [entry({ number: 1, updatedAt: "2026-07-01T00:00:00Z" })] })], diff --git a/apps/web/src/components/pullRequest/pullRequestList.logic.ts b/apps/web/src/components/pullRequest/pullRequestList.logic.ts index 3d996b2e82be..6bb3dfd600a2 100644 --- a/apps/web/src/components/pullRequest/pullRequestList.logic.ts +++ b/apps/web/src/components/pullRequest/pullRequestList.logic.ts @@ -404,11 +404,16 @@ export function partitionPullRequestsWithPriority right.updatedAt.localeCompare(left.updatedAt); return ( [ - { key: "authored", entries: [...authoredByKey.values()].toSorted(byRecency) }, - { key: "reviewRequested", entries: [...reviewByKey.values()].toSorted(byRecency) }, + { + key: "authored", + entries: [...authoredByKey.values()].toSorted(comparePullRequestsByRecency), + }, + { + key: "reviewRequested", + entries: [...reviewByKey.values()].toSorted(comparePullRequestsByRecency), + }, { key: "others", entries: others }, ] as const ) @@ -503,7 +508,7 @@ export function mergePullRequestLists( return { viewers, providers: [...providers.values()], - entries: entries.toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt)), + entries: entries.toSorted(comparePullRequestsByRecency), errors, truncated, nextCursors, @@ -697,7 +702,7 @@ export function rankPullRequestMatches( if (query.trim().length === 0) return entries; return entries.toSorted((left, right) => { const byScore = scorePullRequestMatch(right, query) - scorePullRequestMatch(left, query); - return byScore !== 0 ? byScore : right.updatedAt.localeCompare(left.updatedAt); + return byScore !== 0 ? byScore : comparePullRequestsByRecency(left, right); }); } @@ -718,24 +723,29 @@ export function rankPullRequestsByMergeReadiness( entries: ReadonlyArray, tier: (entry: Entry) => number, ): ReadonlyArray { - const timestamp = (entry: Entry) => toSortableTimestamp(entry.updatedAt); return entries.toSorted((left, right) => { const byTier = tier(left) - tier(right); - if (byTier !== 0) return byTier; - const leftUpdated = timestamp(left); - const rightUpdated = timestamp(right); - const measured = Number(leftUpdated === null) - Number(rightUpdated === null); - if (measured !== 0) return measured; - if (leftUpdated === null || rightUpdated === null) return 0; - return rightUpdated - leftUpdated; + return byTier !== 0 ? byTier : comparePullRequestsByRecency(left, right); }); } diff --git a/apps/web/src/components/pullRequest/pullRequestListGroups.logic.ts b/apps/web/src/components/pullRequest/pullRequestListGroups.logic.ts index 62a3200a6663..f1417a21eec2 100644 --- a/apps/web/src/components/pullRequest/pullRequestListGroups.logic.ts +++ b/apps/web/src/components/pullRequest/pullRequestListGroups.logic.ts @@ -7,6 +7,7 @@ import type { import type { PullRequestTabStatusSeed } from "~/components/RightPanelTabs"; import { pullRequestSurfaceId } from "~/rightPanelStore"; import { + comparePullRequestsByRecency, filterPullRequestsByInvolvement, groupPullRequestsByInvolvement, matchesPullRequestFilters, @@ -68,9 +69,7 @@ export function pullRequestGroups({ ) : involvementEntries; if (text.length === 0) { - return narrowedEntries.toSorted((left, right) => - right.updatedAt.localeCompare(left.updatedAt), - ); + return narrowedEntries.toSorted(comparePullRequestsByRecency); } return narrowedEntries.filter( (entry) =>