diff --git a/packages/communication/src/__tests__/chat-messages.test.ts b/packages/communication/src/__tests__/chat-messages.test.ts index f70cd98302..d1aa642343 100644 --- a/packages/communication/src/__tests__/chat-messages.test.ts +++ b/packages/communication/src/__tests__/chat-messages.test.ts @@ -270,6 +270,18 @@ describe('chat message copy builders', () => { 'Reply anytime · [PR #7](https://github.com/org/repo/pull/7), [PR #8](https://github.com/org/other-repo/pull/8) · [Open in Roomote](https://roomote.dev/task/123)', ); + expect( + buildThreadReplyFooterText({ + taskUrl: 'https://roomote.dev/task/123', + linkedPrs: [7, 8, 9, 10, 11].map((prNumber) => ({ + prNumber, + prUrl: `https://github.com/org/repo/pull/${prNumber}`, + })), + }), + ).toBe( + 'Reply anytime · [PR #7](https://github.com/org/repo/pull/7), [PR #8](https://github.com/org/repo/pull/8), [PR #9](https://github.com/org/repo/pull/9), 2 more PRs · [Open in Roomote](https://roomote.dev/task/123)', + ); + expect( buildThreadReplyFooterText({ taskUrl: 'https://roomote.dev/task/123', diff --git a/packages/communication/src/chat-messages.ts b/packages/communication/src/chat-messages.ts index 9c2691d328..e280709577 100644 --- a/packages/communication/src/chat-messages.ts +++ b/packages/communication/src/chat-messages.ts @@ -294,6 +294,8 @@ export type ThreadReplyRunningTasks = { url: string; }; +const MAX_INLINE_THREAD_REPLY_PRS = 3; + export function buildThreadReplyFooterText({ taskUrl, linkedPrs, @@ -318,10 +320,23 @@ export function buildThreadReplyFooterText({ : `${runningTasks.count} tasks running`, ); } - const prLinks = (linkedPrs ?? []).map((pr) => - formatLink(`PR #${pr.prNumber}`, pr.prUrl), - ); - if (prLinks.length > 0) items.push(prLinks.join(', ')); + const prs = linkedPrs ?? []; + const prLinks = prs + .slice(0, MAX_INLINE_THREAD_REPLY_PRS) + .map((pr) => formatLink(`PR #${pr.prNumber}`, pr.prUrl)); + if (prLinks.length > 0) { + const remainingPrCount = prs.length - prLinks.length; + items.push( + [ + ...prLinks, + ...(remainingPrCount > 0 + ? [ + `${remainingPrCount} more ${remainingPrCount === 1 ? 'PR' : 'PRs'}`, + ] + : []), + ].join(', '), + ); + } const taskNavigationUrl = new URL(taskUrl); let webUrl = taskNavigationUrl; diff --git a/packages/slack/src/__tests__/thread-reply-footer-ops.test.ts b/packages/slack/src/__tests__/thread-reply-footer-ops.test.ts index a481fffaab..438b0f86f1 100644 --- a/packages/slack/src/__tests__/thread-reply-footer-ops.test.ts +++ b/packages/slack/src/__tests__/thread-reply-footer-ops.test.ts @@ -74,6 +74,18 @@ describe('thread-reply-footer-ops', () => { }), ).toBe(true); + expect( + isSlackThreadReplyFooterBlock({ + type: 'context', + elements: [ + { + type: 'mrkdwn', + text: 'Reply anytime · , , , 26 more PRs · ', + }, + ], + }), + ).toBe(true); + expect( isSlackThreadReplyFooterBlock({ type: 'context', diff --git a/packages/slack/src/thread-reply-footer-ops.ts b/packages/slack/src/thread-reply-footer-ops.ts index c12bcf15a2..7de60647f4 100644 --- a/packages/slack/src/thread-reply-footer-ops.ts +++ b/packages/slack/src/thread-reply-footer-ops.ts @@ -32,7 +32,7 @@ export const THREAD_REPLY_FOOTER_LOCK_TIMEOUT_MESSAGE = function isSlackThreadReplyFooterText(text: string): boolean { return ( - /^Reply anytime(?: · (?:1 task|(?:[2-9]|\d{2,}) tasks) running)?(?: · <[^>]+\|PR #\d+>(?:, <[^>]+\|PR #\d+>)*)? · <[^>]+\|Open in Roomote>$/.test( + /^Reply anytime(?: · (?:1 task|(?:[2-9]|\d{2,}) tasks) running)?(?: · <[^>]+\|PR #\d+>(?:, <[^>]+\|PR #\d+>)*(?:, \d+ more PRs?)?)? · <[^>]+\|Open in Roomote>$/.test( text, ) || /^_(?:Reply(?: with @-mention)? or use the <[^>]+\|web app>\.|Working on (?:<[^>]+\|PR(?:\s+#)?\d+>(?:, <[^>]+\|live preview>)?|a <[^>]+\|live preview>), reply(?: with @-mention)? or use the <[^>]+\|web app>\.)_$/.test(