From 0947e69557575bf92f3d410c8d8a2e8d7221c00a Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 15:31:48 -0500 Subject: [PATCH 01/12] Add mode and exclude to FeedClientOptions --- packages/client/src/clients/feed/interfaces.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/client/src/clients/feed/interfaces.ts b/packages/client/src/clients/feed/interfaces.ts index 2f4f163a2..083a3db9b 100644 --- a/packages/client/src/clients/feed/interfaces.ts +++ b/packages/client/src/clients/feed/interfaces.ts @@ -48,6 +48,17 @@ export interface FeedClientOptions { // Optionally set whether to be inclusive of the start and end dates inclusive?: boolean; }; + /** + * The mode to render the feed items in. When `mode` is `compact`, feed items will not have + * `activities` and `total_activities` fields, and the `data` field will not include nested + * arrays and objects. + */ + mode?: "rich" | "compact"; + /** + * Comma-separated list of field paths to exclude from the response. Use dot notation for nested + * fields (e.g., `entries.archived_at`). Limited to 3 levels deep. + */ + exclude?: string; } export type FetchFeedOptions = { From dc3c347d711a425423c29b9e81045cccf2a5c2d9 Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 15:38:04 -0500 Subject: [PATCH 02/12] Update tests --- packages/client/test/clients/feed/feed.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/client/test/clients/feed/feed.test.ts b/packages/client/test/clients/feed/feed.test.ts index f616fe8de..0334f49e1 100644 --- a/packages/client/test/clients/feed/feed.test.ts +++ b/packages/client/test/clients/feed/feed.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test, vi } from "vitest"; +import type { FetchFeedOptions } from "../../../src"; import ApiClient from "../../../src/api"; import Feed from "../../../src/clients/feed/feed"; import { FeedSocketManager } from "../../../src/clients/feed/socket-manager"; @@ -579,10 +580,12 @@ describe("Feed", () => { undefined, ); - const options = { + const options: FetchFeedOptions = { page_size: 25, source: "workflow_123", tenant: "tenant_456", + exclude: "total_actors", + mode: "rich", }; await feed.fetch(options); @@ -595,6 +598,8 @@ describe("Feed", () => { page_size: 25, source: "workflow_123", tenant: "tenant_456", + exclude: "total_actors", + mode: "rich", }, }); } finally { From 14a98d9e5ef7df174d8fc4b77d8d0c445887715c Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 15:45:01 -0500 Subject: [PATCH 03/12] Add changeset --- .changeset/clever-friends-carry.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .changeset/clever-friends-carry.md diff --git a/.changeset/clever-friends-carry.md b/.changeset/clever-friends-carry.md new file mode 100644 index 000000000..56732c15d --- /dev/null +++ b/.changeset/clever-friends-carry.md @@ -0,0 +1,16 @@ +--- +"@knocklabs/client": minor +--- + +Initialize feeds in `"compact"` mode by default + +The feed client can now be initialized with a `mode` option, set to either `"compact"` or `"rich"`. When `mode` is `"compact"`, the `activities` and `total_activities` fields will _not_ be present on feed items. + +```js +const knockFeed = knockClient.feeds.initialize( + process.env.KNOCK_FEED_CHANNEL_ID, + { mode: "compact" }, +); +``` + +**By default, feeds are initialized in `"compact"` mode. If you need to access `activities` and/or `total_activities`, you must initialize your feed in `"rich"` mode.** From 1d15435aacb5f52612d14aa00ecbdea64159f535 Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 15:45:12 -0500 Subject: [PATCH 04/12] Remove exclude --- packages/client/src/clients/feed/interfaces.ts | 7 ++----- packages/client/test/clients/feed/feed.test.ts | 2 -- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/client/src/clients/feed/interfaces.ts b/packages/client/src/clients/feed/interfaces.ts index 083a3db9b..135f657e4 100644 --- a/packages/client/src/clients/feed/interfaces.ts +++ b/packages/client/src/clients/feed/interfaces.ts @@ -52,13 +52,10 @@ export interface FeedClientOptions { * The mode to render the feed items in. When `mode` is `compact`, feed items will not have * `activities` and `total_activities` fields, and the `data` field will not include nested * arrays and objects. + * + * @default "compact" */ mode?: "rich" | "compact"; - /** - * Comma-separated list of field paths to exclude from the response. Use dot notation for nested - * fields (e.g., `entries.archived_at`). Limited to 3 levels deep. - */ - exclude?: string; } export type FetchFeedOptions = { diff --git a/packages/client/test/clients/feed/feed.test.ts b/packages/client/test/clients/feed/feed.test.ts index 0334f49e1..598642660 100644 --- a/packages/client/test/clients/feed/feed.test.ts +++ b/packages/client/test/clients/feed/feed.test.ts @@ -584,7 +584,6 @@ describe("Feed", () => { page_size: 25, source: "workflow_123", tenant: "tenant_456", - exclude: "total_actors", mode: "rich", }; @@ -598,7 +597,6 @@ describe("Feed", () => { page_size: 25, source: "workflow_123", tenant: "tenant_456", - exclude: "total_actors", mode: "rich", }, }); From dbaf175b5ea8a6c616ddd3e7e9d050325961a2b3 Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 15:46:41 -0500 Subject: [PATCH 05/12] Update feedClientDefaults --- packages/client/src/clients/feed/feed.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/clients/feed/feed.ts b/packages/client/src/clients/feed/feed.ts index 21cff1c1f..01518180d 100644 --- a/packages/client/src/clients/feed/feed.ts +++ b/packages/client/src/clients/feed/feed.ts @@ -36,8 +36,9 @@ import { import { getFormattedTriggerData, mergeDateRangeParams } from "./utils"; // Default options to apply -const feedClientDefaults: Pick = { +const feedClientDefaults: Pick = { archived: "exclude", + mode: "compact", }; const DEFAULT_DISCONNECT_DELAY = 2000; From a16267a7413e3ceb2264beb84b63bf33824da758 Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 15:47:35 -0500 Subject: [PATCH 06/12] Add comments to types --- packages/client/src/clients/feed/interfaces.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/client/src/clients/feed/interfaces.ts b/packages/client/src/clients/feed/interfaces.ts index 135f657e4..c71e78c12 100644 --- a/packages/client/src/clients/feed/interfaces.ts +++ b/packages/client/src/clients/feed/interfaces.ts @@ -115,6 +115,10 @@ export type ContentBlock = export interface FeedItem { __cursor: string; id: string; + /** + * List of activities associated with this feed item. + * Only present in "rich" mode. + */ activities: Activity[]; actors: Recipient[]; blocks: ContentBlock[]; @@ -126,6 +130,10 @@ export interface FeedItem { interacted_at: string | null; link_clicked_at: string | null; archived_at: string | null; + /** + * Total number of activities related to this feed item. + * Only present in "rich" mode. + */ total_activities: number; total_actors: number; data: T | null; From f7c5d4664e057093d6f959a867da6636f9b8a047 Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 15:52:32 -0500 Subject: [PATCH 07/12] Fix failing tests --- packages/client/test/clients/feed/feed.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/client/test/clients/feed/feed.test.ts b/packages/client/test/clients/feed/feed.test.ts index 598642660..b014a8029 100644 --- a/packages/client/test/clients/feed/feed.test.ts +++ b/packages/client/test/clients/feed/feed.test.ts @@ -84,6 +84,7 @@ describe("Feed", () => { ); expect(feed.defaultOptions.archived).toBe("exclude"); + expect(feed.defaultOptions.mode).toBe("compact"); } finally { cleanup(); } @@ -547,7 +548,7 @@ describe("Feed", () => { expect(mockApiClient.makeRequest).toHaveBeenCalledWith({ method: "GET", url: "/v1/users/user_123/feeds/01234567-89ab-cdef-0123-456789abcdef", - params: { archived: "exclude" }, + params: { archived: "exclude", mode: "compact" }, }); expect(result).toBeDefined(); if (result && "entries" in result) { @@ -653,6 +654,7 @@ describe("Feed", () => { url: "/v1/users/user_123/feeds/01234567-89ab-cdef-0123-456789abcdef", params: { archived: "exclude", + mode: "compact", after: "cursor_123", }, }); From b1e605464e2a3eef40a4133106804b1b79dbd8ba Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 16:08:08 -0500 Subject: [PATCH 08/12] Update tests --- .../client/test/clients/feed/feed.test.ts | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/packages/client/test/clients/feed/feed.test.ts b/packages/client/test/clients/feed/feed.test.ts index b014a8029..fbde2fda0 100644 --- a/packages/client/test/clients/feed/feed.test.ts +++ b/packages/client/test/clients/feed/feed.test.ts @@ -1587,4 +1587,131 @@ describe("Feed", () => { } }); }); + + describe("Feed Mode", () => { + test("sets mode query param to compact by default", async () => { + const { knock, mockApiClient, cleanup } = getTestSetup(); + + try { + const mockFeedResponse = { + entries: [], + meta: { total_count: 0, unread_count: 0, unseen_count: 0 }, + page_info: { before: null, after: null, page_size: 50 }, + }; + + mockApiClient.makeRequest.mockResolvedValue({ + statusCode: "ok", + body: mockFeedResponse, + }); + + const feed = new Feed( + knock, + "01234567-89ab-cdef-0123-456789abcdef", + {}, + undefined, + ); + + await feed.fetch(); + + expect(mockApiClient.makeRequest).toHaveBeenCalledWith({ + method: "GET", + url: "/v1/users/user_123/feeds/01234567-89ab-cdef-0123-456789abcdef", + params: { + archived: "exclude", + mode: "compact", + }, + }); + } finally { + cleanup(); + } + }); + + test("sets mode query param to rich when initialized in rich mode", async () => { + const { knock, mockApiClient, cleanup } = getTestSetup(); + + try { + const mockFeedResponse = { + entries: [], + meta: { total_count: 0, unread_count: 0, unseen_count: 0 }, + page_info: { before: null, after: null, page_size: 50 }, + }; + + mockApiClient.makeRequest.mockResolvedValue({ + statusCode: "ok", + body: mockFeedResponse, + }); + + const feed = new Feed( + knock, + "01234567-89ab-cdef-0123-456789abcdef", + { mode: "rich" }, + undefined, + ); + + await feed.fetch(); + + expect(mockApiClient.makeRequest).toHaveBeenCalledWith({ + method: "GET", + url: "/v1/users/user_123/feeds/01234567-89ab-cdef-0123-456789abcdef", + params: { + archived: "exclude", + mode: "rich", + }, + }); + } finally { + cleanup(); + } + }); + + test("handles lack of activities and total_activities in compact mode", async () => { + const { knock, mockApiClient, cleanup } = getTestSetup(); + + try { + // Create a compact mode feed item (no activities or total_activities) + const compactFeedItem = { + __cursor: "cursor_123", + id: "msg_123", + actors: [], + blocks: [], + archived_at: null, + inserted_at: new Date().toISOString(), + read_at: null, + seen_at: null, + clicked_at: null, + interacted_at: null, + link_clicked_at: null, + source: { key: "workflow", version_id: "v1", categories: [] }, + tenant: null, + total_actors: 1, + updated_at: new Date().toISOString(), + data: { message: "Hello" }, + }; + + const mockFeedResponse = { + entries: [compactFeedItem], + meta: { total_count: 1, unread_count: 1, unseen_count: 1 }, + page_info: { before: null, after: null, page_size: 50 }, + }; + + mockApiClient.makeRequest.mockResolvedValue({ + statusCode: "ok", + body: mockFeedResponse, + }); + + const feed = new Feed( + knock, + "01234567-89ab-cdef-0123-456789abcdef", + { mode: "compact" }, + undefined, + ); + + const result = await feed.fetch(); + + expect(result).toBeDefined(); + expect(result!.data).toEqual(mockFeedResponse); + } finally { + cleanup(); + } + }); + }); }); From 7dff37eabd10ed6fd3d2b697bc21446853e1dafa Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 16:14:54 -0500 Subject: [PATCH 09/12] Fix useNotifications test --- packages/react-core/test/feed/useNotifications.test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-core/test/feed/useNotifications.test.tsx b/packages/react-core/test/feed/useNotifications.test.tsx index 8f97040bf..e89f1f023 100644 --- a/packages/react-core/test/feed/useNotifications.test.tsx +++ b/packages/react-core/test/feed/useNotifications.test.tsx @@ -20,6 +20,7 @@ describe("useNotifications", () => { archived: "include", page_size: 10, status: "all", + mode: "rich", }; const { result } = renderHook( @@ -127,6 +128,7 @@ describe("useNotifications", () => { archived: "include", page_size: 10, status: "all", + mode: "rich", }; const { result, rerender } = renderHook( @@ -162,12 +164,14 @@ describe("useNotifications", () => { archived: "include", page_size: 10, status: "all", + mode: "compact", }; const options2: FeedClientOptions = { archived: "exclude", page_size: 10, status: "read", + mode: "rich", }; const { result, rerender } = renderHook( From 4980591edca2d8bae56ac549fb67257c880cc61e Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 16:18:29 -0500 Subject: [PATCH 10/12] Make activities and total_activities optional --- packages/client/src/clients/feed/interfaces.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/clients/feed/interfaces.ts b/packages/client/src/clients/feed/interfaces.ts index c71e78c12..dcda7b6c7 100644 --- a/packages/client/src/clients/feed/interfaces.ts +++ b/packages/client/src/clients/feed/interfaces.ts @@ -119,7 +119,7 @@ export interface FeedItem { * List of activities associated with this feed item. * Only present in "rich" mode. */ - activities: Activity[]; + activities?: Activity[]; actors: Recipient[]; blocks: ContentBlock[]; inserted_at: string; @@ -134,7 +134,7 @@ export interface FeedItem { * Total number of activities related to this feed item. * Only present in "rich" mode. */ - total_activities: number; + total_activities?: number; total_actors: number; data: T | null; source: NotificationSource; From e6014f43707d295a604781b54540e3c25c7f7703 Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 16:44:05 -0500 Subject: [PATCH 11/12] Update changeset --- .changeset/clever-friends-carry.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/.changeset/clever-friends-carry.md b/.changeset/clever-friends-carry.md index 56732c15d..632aea61e 100644 --- a/.changeset/clever-friends-carry.md +++ b/.changeset/clever-friends-carry.md @@ -1,16 +1,41 @@ --- "@knocklabs/client": minor +"@knocklabs/react-core": minor +"@knocklabs/expo": minor +"@knocklabs/react": minor +"@knocklabs/react-native": minor --- Initialize feeds in `"compact"` mode by default The feed client can now be initialized with a `mode` option, set to either `"compact"` or `"rich"`. When `mode` is `"compact"`, the `activities` and `total_activities` fields will _not_ be present on feed items. +**By default, feeds are initialized in `"compact"` mode. If you need to access `activities` and/or `total_activities`, you must initialize your feed in `"rich"` mode.** + +If you are using the feed client via `@knocklabs/client` directly: + ```js const knockFeed = knockClient.feeds.initialize( process.env.KNOCK_FEED_CHANNEL_ID, - { mode: "compact" }, + { mode: "full" }, ); ``` -**By default, feeds are initialized in `"compact"` mode. If you need to access `activities` and/or `total_activities`, you must initialize your feed in `"rich"` mode.** +If you are using `` via `@knocklabs/react`, `@knocklabs/react-native`, or `@knocklabs/expo`: + +```tsx + +``` + +If you are using the `useNotifications` hook via `@knocklabs/react-core`: + +```js +const feedClient = useNotifications( + knockClient, + process.env.KNOCK_FEED_CHANNEL_ID, + { mode: "full" }, +); +``` From db047463ce6b534e1994f3945e97df7ca774ecd0 Mon Sep 17 00:00:00 2001 From: Matthew Mikolay Date: Tue, 3 Feb 2026 17:18:27 -0500 Subject: [PATCH 12/12] Update changeset --- .changeset/clever-friends-carry.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/clever-friends-carry.md b/.changeset/clever-friends-carry.md index 632aea61e..80d9e7f69 100644 --- a/.changeset/clever-friends-carry.md +++ b/.changeset/clever-friends-carry.md @@ -8,9 +8,9 @@ Initialize feeds in `"compact"` mode by default -The feed client can now be initialized with a `mode` option, set to either `"compact"` or `"rich"`. When `mode` is `"compact"`, the `activities` and `total_activities` fields will _not_ be present on feed items. +The feed client can now be initialized with a `mode` option, set to either `"compact"` or `"rich"`. When `mode` is `"compact"`, the `activities` and `total_activities` fields will _not_ be present on feed items, and the `data` field will not include nested arrays and objects. -**By default, feeds are initialized in `"compact"` mode. If you need to access `activities` and/or `total_activities`, you must initialize your feed in `"rich"` mode.** +**By default, feeds are initialized in `"compact"` mode. If you need to access `activities`, `total_activities`, or the complete `data`, you must initialize your feed in `"rich"` mode.** If you are using the feed client via `@knocklabs/client` directly: