diff --git a/packages/core/src/inbox/reportFiltering.test.ts b/packages/core/src/inbox/reportFiltering.test.ts index 59c025ba1..c5fcfc97a 100644 --- a/packages/core/src/inbox/reportFiltering.test.ts +++ b/packages/core/src/inbox/reportFiltering.test.ts @@ -99,12 +99,25 @@ describe("filterReportsBySearch", () => { describe("buildSignalReportListOrdering", () => { it.each([ - ["total_weight", "desc", "status,-total_weight"], - ["created_at", "asc", "status,created_at"], - ["signal_count", "desc", "status,-signal_count"], - ] as const)("orders by status then %s (%s)", (field, direction, expected) => { - expect(buildSignalReportListOrdering(field, direction)).toBe(expected); - }); + ["total_weight", "desc", "status,-total_weight,priority"], + ["created_at", "asc", "status,created_at,priority"], + ["signal_count", "desc", "status,-signal_count,priority"], + ] as const)( + "orders by status then %s (%s), tiebreaking by priority", + (field, direction, expected) => { + expect(buildSignalReportListOrdering(field, direction)).toBe(expected); + }, + ); + + it.each([ + ["priority", "asc", "status,priority,-created_at"], + ["priority", "desc", "status,-priority,-created_at"], + ] as const)( + "tiebreaks %s (%s) by newest first", + (field, direction, expected) => { + expect(buildSignalReportListOrdering(field, direction)).toBe(expected); + }, + ); it("does not float the current user's reports via ordering", () => { expect(buildSignalReportListOrdering("priority", "asc")).not.toContain( diff --git a/packages/core/src/inbox/reportFiltering.ts b/packages/core/src/inbox/reportFiltering.ts index d6cb760a9..170b65db1 100644 --- a/packages/core/src/inbox/reportFiltering.ts +++ b/packages/core/src/inbox/reportFiltering.ts @@ -65,6 +65,11 @@ export function buildStatusFilterParam(statuses: SignalReportStatus[]): string { * Comma-separated `ordering` for the signal report list API: * 1. Status rank (ready first – semantic server-side rank, always applied) * 2. Toolbar-selected field (priority, total_weight, created_at, etc.) + * 3. A tiebreak so reports the primary field can't separate come back in a + * sensible order. Sorting by priority (a coarse 5-bucket P0–P4 rank) tiebreaks + * by `-created_at` so the newest report wins within a tier; every other field + * tiebreaks by `priority` so the most urgent report wins. The server applies + * the clauses in order (and falls back to `id`), so this only breaks ties. * * Reviewer scope is applied via the `suggested_reviewers` param, not ordering: * a `-is_suggested_reviewer` tiebreak would float the user's reports to the top @@ -75,7 +80,8 @@ export function buildSignalReportListOrdering( direction: "asc" | "desc", ): string { const fieldKey = direction === "desc" ? `-${field}` : field; - return `status,${fieldKey}`; + const tiebreak = field === "priority" ? "-created_at" : "priority"; + return ["status", fieldKey, tiebreak].join(","); } export function buildSuggestedReviewerFilterParam(