From 3ba1edf6c3440fda257cdfded1d8024a6c898bbf Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Mon, 8 Jun 2026 11:35:00 +0100 Subject: [PATCH 1/2] feat(mobile): add Scout as an inbox signal source (port #2505) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the mobile inbox aware of the `signals_scout` source product so Scout-sourced signal reports display and filter correctly. - Add `"signals_scout"` to the `SourceProduct` union in the inbox filter store - Add a "Scout" option to the FilterSheet source options - Render the Compass icon and a "Scout · Cross-source issue" / "Scout" label in SignalCard Ports PostHog/code#2505 to apps/mobile. Generated-By: PostHog Code Task-Id: 9218c738-5580-4836-acd6-4a220919cc8a --- .../features/inbox/components/FilterSheet.tsx | 1 + .../features/inbox/components/SignalCard.tsx | 9 +++++ .../inbox/stores/inboxFilterStore.test.ts | 34 ++++++++++++++++--- .../features/inbox/stores/inboxFilterStore.ts | 3 +- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/apps/mobile/src/features/inbox/components/FilterSheet.tsx b/apps/mobile/src/features/inbox/components/FilterSheet.tsx index 9c05fd1c1..5243c6764 100644 --- a/apps/mobile/src/features/inbox/components/FilterSheet.tsx +++ b/apps/mobile/src/features/inbox/components/FilterSheet.tsx @@ -76,6 +76,7 @@ const SOURCE_PRODUCT_OPTIONS: { value: SourceProduct; label: string }[] = [ { value: "linear", label: "Linear" }, { value: "zendesk", label: "Zendesk" }, { value: "conversations", label: "Conversations" }, + { value: "signals_scout", label: "Scout" }, ]; function SectionHeader({ title }: { title: string }) { diff --git a/apps/mobile/src/features/inbox/components/SignalCard.tsx b/apps/mobile/src/features/inbox/components/SignalCard.tsx index a8c871828..a32ff002d 100644 --- a/apps/mobile/src/features/inbox/components/SignalCard.tsx +++ b/apps/mobile/src/features/inbox/components/SignalCard.tsx @@ -7,6 +7,7 @@ import { ChatCircle, CheckCircle, Code, + Compass, GithubLogo, LinkSimple, Question, @@ -46,6 +47,12 @@ function sourceLine(signal: Signal): string { return "GitHub · Issue"; if (source_product === "linear" && source_type === "issue") return "Linear · Issue"; + if ( + source_product === "signals_scout" && + source_type === "cross_source_issue" + ) + return "Scout · Cross-source issue"; + if (source_product === "signals_scout") return "Scout"; const product = source_product.replace(/_/g, " "); const type = source_type.replace(/_/g, " "); return `${product} · ${type}`; @@ -73,6 +80,8 @@ function SourceIcon({ return ; case "linear": return ; + case "signals_scout": + return ; default: return ; } diff --git a/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts b/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts index 31cae025d..776d1a7e7 100644 --- a/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts +++ b/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts @@ -1,14 +1,40 @@ -import { beforeEach, describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("@react-native-async-storage/async-storage", () => ({ + default: { + getItem: vi.fn(), + setItem: vi.fn(), + removeItem: vi.fn(), + }, +})); import { useInboxFilterStore } from "./inboxFilterStore"; -const INITIAL_STATE = useInboxFilterStore.getState(); +describe("inboxFilterStore", () => { + beforeEach(() => { + useInboxFilterStore.getState().resetFilters(); + }); + + it("toggles signals_scout in and out of the source filter", () => { + const { toggleSourceProduct } = useInboxFilterStore.getState(); + + toggleSourceProduct("signals_scout"); + expect(useInboxFilterStore.getState().sourceProductFilter).toEqual([ + "signals_scout", + ]); -beforeEach(() => { - useInboxFilterStore.setState(INITIAL_STATE, true); + toggleSourceProduct("signals_scout"); + expect(useInboxFilterStore.getState().sourceProductFilter).toEqual([]); + }); }); +const INITIAL_STATE = useInboxFilterStore.getState(); + describe("inboxFilterStore priority filter", () => { + beforeEach(() => { + useInboxFilterStore.setState(INITIAL_STATE, true); + }); + it("starts empty (no priority filter)", () => { expect(useInboxFilterStore.getState().priorityFilter).toEqual([]); }); diff --git a/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts b/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts index e777738bf..ea363b4e4 100644 --- a/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts +++ b/apps/mobile/src/features/inbox/stores/inboxFilterStore.ts @@ -21,7 +21,8 @@ export type SourceProduct = | "github" | "linear" | "zendesk" - | "conversations"; + | "conversations" + | "signals_scout"; export const DEFAULT_STATUS_FILTER: SignalReportStatus[] = [ "ready", From b4de3eabcd808f550a0201c236c0d792c38ad068 Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Mon, 8 Jun 2026 11:50:59 +0100 Subject: [PATCH 2/2] test(mobile): parameterize inbox source-filter toggle test Convert the source-filter toggle test to a table-driven `it.each` over multiple SourceProduct values, per team preference. Makes it trivial to extend as new source products are added. Generated-By: PostHog Code Task-Id: 9218c738-5580-4836-acd6-4a220919cc8a --- .../inbox/stores/inboxFilterStore.test.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts b/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts index 776d1a7e7..e8dfa483f 100644 --- a/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts +++ b/apps/mobile/src/features/inbox/stores/inboxFilterStore.test.ts @@ -8,24 +8,27 @@ vi.mock("@react-native-async-storage/async-storage", () => ({ }, })); -import { useInboxFilterStore } from "./inboxFilterStore"; +import { type SourceProduct, useInboxFilterStore } from "./inboxFilterStore"; describe("inboxFilterStore", () => { beforeEach(() => { useInboxFilterStore.getState().resetFilters(); }); - it("toggles signals_scout in and out of the source filter", () => { - const { toggleSourceProduct } = useInboxFilterStore.getState(); + it.each(["signals_scout", "error_tracking", "github"])( + "toggles %s in and out of the source filter", + (source) => { + const { toggleSourceProduct } = useInboxFilterStore.getState(); - toggleSourceProduct("signals_scout"); - expect(useInboxFilterStore.getState().sourceProductFilter).toEqual([ - "signals_scout", - ]); + toggleSourceProduct(source); + expect(useInboxFilterStore.getState().sourceProductFilter).toEqual([ + source, + ]); - toggleSourceProduct("signals_scout"); - expect(useInboxFilterStore.getState().sourceProductFilter).toEqual([]); - }); + toggleSourceProduct(source); + expect(useInboxFilterStore.getState().sourceProductFilter).toEqual([]); + }, + ); }); const INITIAL_STATE = useInboxFilterStore.getState();