diff --git a/apps/mobile/src/features/chat/utils/thinkingMessages.ts b/apps/mobile/src/features/chat/utils/thinkingMessages.ts
index 35bd41b351..2eee2a1de4 100644
--- a/apps/mobile/src/features/chat/utils/thinkingMessages.ts
+++ b/apps/mobile/src/features/chat/utils/thinkingMessages.ts
@@ -60,7 +60,6 @@ export const THINKING_MESSAGES = [
"Blooming",
"Sparking",
"Nesting",
- "Looping",
"Wiring",
"Snipping",
"Zoning",
diff --git a/packages/core/src/inbox/engagement.test.ts b/packages/core/src/inbox/engagement.test.ts
index 4256dced8f..f880ace30c 100644
--- a/packages/core/src/inbox/engagement.test.ts
+++ b/packages/core/src/inbox/engagement.test.ts
@@ -65,6 +65,19 @@ describe("buildBulkActionEvents", () => {
expect(events.every((e) => e.action_type === "delete")).toBe(true);
});
+ it("passes through the remove-suggested-reviewer action type", () => {
+ const events = buildBulkActionEvents({
+ reports: [fakeReport({ id: "a" }), fakeReport({ id: "b" })],
+ actionType: "remove_suggested_reviewer",
+ surface: "toolbar",
+ });
+
+ expect(
+ events.every((e) => e.action_type === "remove_suggested_reviewer"),
+ ).toBe(true);
+ expect(events.every((e) => e.dismissal_reason === undefined)).toBe(true);
+ });
+
it("attaches dismissal reason/note only for dismiss, truncating the note", () => {
const longNote = "x".repeat(600);
const [dismissed] = buildBulkActionEvents({
diff --git a/packages/core/src/inbox/engagement.ts b/packages/core/src/inbox/engagement.ts
index bb31915e35..dc4428ddbb 100644
--- a/packages/core/src/inbox/engagement.ts
+++ b/packages/core/src/inbox/engagement.ts
@@ -123,7 +123,7 @@ export function resolveActionProperties(
/** Bulk-capable report actions fired from the selection toolbar / dismiss flows. */
export type InboxBulkActionType = Extract<
InboxReportActionProperties["action_type"],
- "dismiss" | "snooze" | "delete" | "reingest"
+ "dismiss" | "snooze" | "delete" | "reingest" | "remove_suggested_reviewer"
>;
export interface BuildBulkActionEventsInput {
@@ -137,8 +137,9 @@ export interface BuildBulkActionEventsInput {
/**
* Build `INBOX_REPORT_ACTION` payloads for a bulk (or single-report) dismiss /
- * snooze / delete / reingest. Pure so it can be unit-tested and reused across
- * the toolbar, the per-row dismiss action, and detail-screen dismiss.
+ * snooze / delete / reingest / remove-suggested-reviewer. Pure so it can be
+ * unit-tested and reused across the toolbar, the per-row dismiss action, and
+ * detail-screen dismiss.
*
* `is_bulk` / `bulk_size` carry the grouping; `rank` / `list_size` are left at 0
* because these flows act on a selection, not a positional list slot.
diff --git a/packages/core/src/sessions/titleGeneratorService.test.ts b/packages/core/src/sessions/titleGeneratorService.test.ts
index 956fd897ac..dfa5829191 100644
--- a/packages/core/src/sessions/titleGeneratorService.test.ts
+++ b/packages/core/src/sessions/titleGeneratorService.test.ts
@@ -220,6 +220,42 @@ describe("generateTitleAndSummary", () => {
expect(result?.title).toBe("Fix login bug");
});
+ it("does not parse SUMMARY in a PR title as the summary", async () => {
+ prompt.mockResolvedValue({
+ content:
+ "TITLE: Review PR #123: Fix SUMMARY: parsing\nSUMMARY: Fixing title and summary parsing.",
+ });
+
+ const result = await makeService().generateTitleAndSummary(
+ '',
+ );
+
+ expect(result).toEqual({
+ title: "Review PR #123: Fix SUMMARY: parsing",
+ summary: "Fixing title and summary parsing.",
+ });
+ });
+
+ it("instructs the model to include existing GitHub PR titles", async () => {
+ prompt.mockResolvedValue({
+ content:
+ "TITLE: Review PR #123: Fix login redirect\nSUMMARY: Reviewing the existing pull request.",
+ });
+
+ await makeService().generateTitleAndSummary(
+ '',
+ );
+
+ expect(prompt).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({
+ system: expect.stringContaining(
+ "the generated TITLE MUST include both the PR number and the PR title verbatim",
+ ),
+ }),
+ );
+ });
+
it("returns null on error", async () => {
prompt.mockRejectedValue(new Error("network error"));
const result = await makeService().generateTitleAndSummary("some content");
diff --git a/packages/core/src/sessions/titleGeneratorService.ts b/packages/core/src/sessions/titleGeneratorService.ts
index 537317fb02..7913c2f64b 100644
--- a/packages/core/src/sessions/titleGeneratorService.ts
+++ b/packages/core/src/sessions/titleGeneratorService.ts
@@ -40,6 +40,7 @@ Title rules:
- Remove: the, this, my, a, an
- If possible, start with action verbs (Fix, Implement, Analyze, Debug, Update, Research, Review)
- Keep exact: technical terms, numbers, filenames, HTTP codes, PR numbers
+- GitHub PR rule: If the content contains a with a non-empty title, the generated TITLE MUST include both the PR number and the PR title verbatim. This rule overrides the 6-word title limit. Never replace the PR title with a generic phrase. Before responding, verify that both values appear in TITLE.
- Never assume tech stack
- Only output "Untitled" if the input is completely null/missing, not just unclear
- If the input is a URL (e.g. a GitHub issue link, PR link, or any web URL), generate a title based on what you can infer from the URL structure (repo name, issue/PR number, etc.). Never say you cannot access URLs or ask the user for more information.
@@ -58,6 +59,7 @@ Title examples:
- "Update user documentation for new API endpoints" → Update API documentation
- "Research competitor pricing strategies for our product" → Research competitor pricing
- "Review pull request #123" → Review pull request #123
+- "" → Review PR #123: Fix login redirect
- "debug 500 errors in production" → Debug production 500 errors
- "why is the payment flow failing" → Analyze payment flow failure
- "So how about that weather huh" → Weather chat
@@ -173,7 +175,7 @@ export class TitleGeneratorService {
const text = result.content.trim();
const titleMatch = text.match(/^TITLE:\s*(.+?)(?:\n|$)/m);
- const summaryMatch = text.match(/SUMMARY:\s*([\s\S]+)$/m);
+ const summaryMatch = text.match(/^SUMMARY:\s*([\s\S]+)$/m);
const title =
titleMatch?.[1]
diff --git a/packages/ui/src/features/inbox/components/InboxBulkSelectionBar.tsx b/packages/ui/src/features/inbox/components/InboxBulkSelectionBar.tsx
index 263b2b5d4b..9a6e96e28b 100644
--- a/packages/ui/src/features/inbox/components/InboxBulkSelectionBar.tsx
+++ b/packages/ui/src/features/inbox/components/InboxBulkSelectionBar.tsx
@@ -3,6 +3,7 @@ import {
EyeSlashIcon,
PauseIcon,
TrashIcon,
+ UserMinusIcon,
XIcon,
} from "@phosphor-icons/react";
import { isDismissalReasonSnooze } from "@posthog/shared/dismissalReasons";
@@ -158,6 +159,24 @@ export function InboxBulkSelectionBar({
Reingest
+
+