From 70909312f31ea3b42539c26fbb6017203461ba40 Mon Sep 17 00:00:00 2001 From: AmarTrebinjac Date: Wed, 15 Apr 2026 10:54:37 +0000 Subject: [PATCH] fix: truncate X notification attachment titles to 140 characters SocialTwitter post titles in notification attachments were displayed in full, causing oversized notifications. Truncate to 140 characters with ellipsis for SocialTwitter posts only. Co-Authored-By: Claude Opus 4.6 --- __tests__/notifications/index.ts | 55 ++++++++++++++++++++++++++++++++ src/notifications/builder.ts | 10 +++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/__tests__/notifications/index.ts b/__tests__/notifications/index.ts index edaf688975..2ec413ec92 100644 --- a/__tests__/notifications/index.ts +++ b/__tests__/notifications/index.ts @@ -1490,6 +1490,61 @@ describe('storeNotificationBundle', () => { ]); }); + it('should truncate attachment title for SocialTwitter posts exceeding 140 characters', () => { + const type = NotificationType.SourcePostAdded; + const longTitle = 'a'.repeat(200); + const post = { + ...postsFixture[0], + title: longTitle, + type: PostType.SocialTwitter, + } as Reference; + const ctx: NotificationSourceContext & NotificationPostContext = { + userIds: [userId], + source: sourcesFixture[0] as Reference, + post, + }; + const actual = generateNotificationV2(type, ctx); + + expect(actual.attachments![0].title).toEqual(`${'a'.repeat(137)}...`); + expect(actual.attachments![0].title!.length).toEqual(140); + }); + + it('should not truncate attachment title for SocialTwitter posts within 140 characters', () => { + const type = NotificationType.SourcePostAdded; + const shortTitle = 'a'.repeat(140); + const post = { + ...postsFixture[0], + title: shortTitle, + type: PostType.SocialTwitter, + } as Reference; + const ctx: NotificationSourceContext & NotificationPostContext = { + userIds: [userId], + source: sourcesFixture[0] as Reference, + post, + }; + const actual = generateNotificationV2(type, ctx); + + expect(actual.attachments![0].title).toEqual(shortTitle); + }); + + it('should not truncate attachment title for non-SocialTwitter posts', () => { + const type = NotificationType.SourcePostAdded; + const longTitle = 'a'.repeat(200); + const post = { + ...postsFixture[0], + title: longTitle, + type: PostType.Article, + } as Reference; + const ctx: NotificationSourceContext & NotificationPostContext = { + userIds: [userId], + source: sourcesFixture[0] as Reference, + post, + }; + const actual = generateNotificationV2(type, ctx); + + expect(actual.attachments![0].title).toEqual(longTitle); + }); + it('should generate user_post_added notification', () => { const type = NotificationType.UserPostAdded; const ctx: NotificationUserContext & NotificationPostContext = { diff --git a/src/notifications/builder.ts b/src/notifications/builder.ts index 68aa7c6e66..529f21a7e1 100644 --- a/src/notifications/builder.ts +++ b/src/notifications/builder.ts @@ -311,10 +311,18 @@ export class NotificationBuilder { post.type as keyof typeof postTypeToAttachmentType ] ?? NotificationAttachmentType.Post; + const MAX_TWITTER_TITLE_LENGTH = 140; + const title = post.title ?? ''; + const truncatedTitle = + post.type === PostType.SocialTwitter && + title.length > MAX_TWITTER_TITLE_LENGTH + ? `${title.substring(0, MAX_TWITTER_TITLE_LENGTH - 3)}...` + : title; + this.attachments.push({ type, image: (post as ArticlePost)?.image || pickImageUrl(post), - title: post.title ?? '', + title: truncatedTitle, referenceId: post.id, }); return this;