From f7bae829cf53ca7aef958b6a4e2647de373ce464 Mon Sep 17 00:00:00 2001 From: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Date: Thu, 16 Jul 2026 20:10:51 -0400 Subject: [PATCH] fix(desktop): add channel names to feed notifications Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> --- desktop/src/app/AppShell.tsx | 1 + desktop/src/features/notifications/hooks.ts | 4 +- .../features/notifications/lib/feed.test.mjs | 58 +++++++++++++++++++ .../src/features/notifications/lib/feed.ts | 24 +++++++- .../use-feed-desktop-notifications.ts | 7 ++- 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 desktop/src/features/notifications/lib/feed.test.mjs diff --git a/desktop/src/app/AppShell.tsx b/desktop/src/app/AppShell.tsx index ff21b07a6..05f8e2581 100644 --- a/desktop/src/app/AppShell.tsx +++ b/desktop/src/app/AppShell.tsx @@ -373,6 +373,7 @@ export function AppShell() { threadActivityFeedItems, getThreadReadAt, getMessageReadAt, + channels, ); const dueReminderBadge = useDueReminderBadgeCount( diff --git a/desktop/src/features/notifications/hooks.ts b/desktop/src/features/notifications/hooks.ts index e039d0eae..ccc9544f9 100644 --- a/desktop/src/features/notifications/hooks.ts +++ b/desktop/src/features/notifications/hooks.ts @@ -3,7 +3,7 @@ import * as React from "react"; import { useHomeFeedQuery } from "@/features/home/hooks"; import { useUsersBatchQuery } from "@/features/profile/hooks"; import type { UserProfileLookup } from "@/features/profile/lib/identity"; -import type { FeedItem, HomeFeedResponse } from "@/shared/api/types"; +import type { Channel, FeedItem, HomeFeedResponse } from "@/shared/api/types"; import { getDesktopNotificationPermissionState, requestDesktopNotificationAccess, @@ -376,6 +376,7 @@ export function useHomeFeedNotificationState( // has been revealed in-channel, even if the aggregate `thread:` marker // has not been advanced by opening Home. getMessageReadAt: (messageId: string) => number | null = () => null, + channels: ReadonlyArray> = [], ) { useFeedDesktopNotifications( feed, @@ -384,6 +385,7 @@ export function useHomeFeedNotificationState( setDesktopEnabled, profiles, mutedChannelIds, + channels, ); const normalizedPubkey = pubkey?.trim().toLowerCase() ?? ""; const [seenFeedIds, setSeenFeedIds] = React.useState(() => diff --git a/desktop/src/features/notifications/lib/feed.test.mjs b/desktop/src/features/notifications/lib/feed.test.mjs new file mode 100644 index 000000000..4f1c40f55 --- /dev/null +++ b/desktop/src/features/notifications/lib/feed.test.mjs @@ -0,0 +1,58 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { enrichFeedItemChannel, notificationTitle } from "./feed.ts"; + +const feedItem = (overrides = {}) => ({ + id: "event-id", + kind: 46011, + pubkey: "author", + content: "Please review", + createdAt: 1, + channelId: "channel-id", + channelName: "", + channelType: undefined, + tags: [["h", "channel-id"]], + category: "needs_action", + ...overrides, +}); + +const channels = [ + { id: "channel-id", name: "ship-room", channelType: "stream" }, +]; + +test("enriches a feed notification with its loaded channel name", () => { + const item = enrichFeedItemChannel(feedItem(), channels); + + assert.equal(item.channelName, "ship-room"); + assert.equal(item.channelType, "stream"); + assert.equal(notificationTitle(item), "Needs Action in #ship-room"); +}); + +test("preserves feed-provided channel metadata", () => { + const original = feedItem({ + channelName: "backend-name", + channelType: "forum", + }); + const item = enrichFeedItemChannel(original, channels); + + assert.equal(item, original); + assert.equal(notificationTitle(item), "Needs Action in #backend-name"); +}); + +test("falls back safely when the channel list has not loaded the channel", () => { + const original = feedItem(); + const item = enrichFeedItemChannel(original, []); + + assert.equal(item, original); + assert.equal(notificationTitle(item), "Needs Action"); +}); + +test("does not replace direct-message notification titles", () => { + const original = feedItem({ channelType: "dm" }); + const item = enrichFeedItemChannel(original, [ + { id: "channel-id", name: "not-a-title", channelType: "dm" }, + ]); + + assert.equal(notificationTitle(item, "Taylor"), "Taylor"); +}); diff --git a/desktop/src/features/notifications/lib/feed.ts b/desktop/src/features/notifications/lib/feed.ts index ff468e841..85a0680ad 100644 --- a/desktop/src/features/notifications/lib/feed.ts +++ b/desktop/src/features/notifications/lib/feed.ts @@ -1,9 +1,31 @@ -import type { FeedItem, HomeFeedResponse } from "@/shared/api/types"; +import type { Channel, FeedItem, HomeFeedResponse } from "@/shared/api/types"; import { formatNotificationTitle, truncateNotificationBody, } from "@/features/notifications/lib/notificationFormat"; +export type NotificationChannel = Pick; + +export function enrichFeedItemChannel( + item: FeedItem, + channels: readonly NotificationChannel[], +): FeedItem { + if (!item.channelId || item.channelName.trim()) { + return item; + } + + const channel = channels.find((candidate) => candidate.id === item.channelId); + if (!channel) { + return item; + } + + return { + ...item, + channelName: channel.name, + channelType: item.channelType ?? channel.channelType, + }; +} + export function notificationTitle(item: FeedItem, senderName?: string) { const channelLabel = item.channelType !== "dm" && item.channelName.trim() diff --git a/desktop/src/features/notifications/use-feed-desktop-notifications.ts b/desktop/src/features/notifications/use-feed-desktop-notifications.ts index ac2373c31..501a8fb65 100644 --- a/desktop/src/features/notifications/use-feed-desktop-notifications.ts +++ b/desktop/src/features/notifications/use-feed-desktop-notifications.ts @@ -10,6 +10,8 @@ import type { FeedItem, HomeFeedResponse } from "@/shared/api/types"; import { collectHomeAlertItems, eligibleFeedNotificationItems, + enrichFeedItemChannel, + type NotificationChannel, notificationBody, notificationTitle, } from "./lib/feed"; @@ -74,6 +76,7 @@ export function useFeedDesktopNotifications( setDesktopEnabled: (enabled: boolean) => Promise, profiles?: UserProfileLookup, mutedChannelIds?: ReadonlySet, + channels: readonly NotificationChannel[] = [], ) { const normalizedPubkey = pubkey?.trim().toLowerCase() ?? ""; const seenItemIdsRef = React.useRef>( @@ -192,7 +195,8 @@ export function useFeedDesktopNotifications( void autoRequestPermissionIfNeeded(); } - for (const item of newItems) { + for (const rawItem of newItems) { + const item = enrichFeedItemChannel(rawItem, channels); const resolvedLabel = profiles ? resolveUserLabel({ pubkey: item.pubkey, @@ -209,6 +213,7 @@ export function useFeedDesktopNotifications( } }, [ feed, + channels, mutedChannelIds, normalizedPubkey, profiles,