Fix AI sparkle indicator on consecutive agent posts (#37064)

Exclude AI-generated posts from consecutive-post collapsing so each
agent message keeps a visible header and sparkle badge. Also simplify
the badge render condition to always show the indicator for AI posts
in the badges area when appropriate.

Co-authored-by: Mattermost Build <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
Nick Misasi
2026-06-25 08:51:18 -04:00
committed by GitHub
co-authored by Hanzo Dev
parent 46a4e86f4d
commit 195d2e96a3
4 changed files with 74 additions and 6 deletions
@@ -670,7 +670,7 @@ describe('PostComponent', () => {
expect(screen.queryByLabelText(/AI-generated|Message posted by/)).not.toBeInTheDocument();
});
test('should not show AI-generated indicator for consecutive posts', () => {
test('should show AI-generated indicator for consecutive AI posts', () => {
const props = {
...baseProps,
post: aiGeneratedPost,
@@ -679,7 +679,7 @@ describe('PostComponent', () => {
};
renderWithContext(<PostComponent {...props}/>);
expect(screen.queryByLabelText(/AI-generated|Message posted by/)).not.toBeInTheDocument();
expect(screen.getByLabelText('Message posted by @aibot')).toBeInTheDocument();
});
test('should show AI-generated indicator in PostUserProfile for compact mode in CENTER', () => {
@@ -697,7 +697,7 @@ describe('PostComponent', () => {
expect(indicators.length).toBe(1);
});
test('should hide AI-generated indicator for consecutive posts in threads', () => {
test('should show AI-generated indicator for consecutive AI posts in threads', () => {
const threadPost = TestHelper.getPostMock({
channel_id: channel.id,
root_id: 'root_post_id',
@@ -715,7 +715,7 @@ describe('PostComponent', () => {
};
renderWithContext(<PostComponent {...props}/>);
expect(screen.queryByLabelText(/AI-generated|Message posted by/)).not.toBeInTheDocument();
expect(screen.getByLabelText('Message posted by @aibot')).toBeInTheDocument();
});
test('should show AI-generated indicator for non-consecutive posts in threads', () => {
@@ -855,8 +855,8 @@ function PostComponent(props: Props) {
{priority}
{burnOnReadBadge}
{burnOnReadTimerChip}
{((!props.compactDisplay && !(hasSameRoot(props) && props.isConsecutivePost)) || (props.compactDisplay && isRHS)) &&
PostUtils.hasAiGeneratedMetadata(post) && (
{PostUtils.hasAiGeneratedMetadata(post) &&
((!props.compactDisplay) || (props.compactDisplay && isRHS)) && (
<AiGeneratedIndicator
userId={post.props.ai_generated_by as string}
username={post.props.ai_generated_by_username as string}
@@ -1584,6 +1584,70 @@ describe('makeGetUniqueEmojiNameReactionsForPost', () => {
});
});
describe('PostUtils.areConsecutivePostsBySameUser', () => {
const userId = 'user_id_1';
const baseTime = 1_000_000;
const makePost = (override: Partial<Post> = {}): Post => {
return TestHelper.getPostMock({
user_id: userId,
create_at: baseTime,
type: '',
...override,
});
};
test('should return true for consecutive posts from the same user', () => {
const previousPost = makePost({create_at: baseTime});
const post = makePost({create_at: baseTime + 1000});
expect(PostUtils.areConsecutivePostsBySameUser(post, previousPost)).toBe(true);
});
test('should return false when current post is AI-generated', () => {
const previousPost = makePost({create_at: baseTime});
const post = makePost({
create_at: baseTime + 1000,
props: {
ai_generated_by: 'ai_user_id',
ai_generated_by_username: 'aibot',
},
});
expect(PostUtils.areConsecutivePostsBySameUser(post, previousPost)).toBe(false);
});
test('should return false when current AI post follows another AI post from the same user', () => {
const aiProps = {
ai_generated_by: 'ai_user_id',
ai_generated_by_username: 'aibot',
};
const previousPost = makePost({
create_at: baseTime,
props: aiProps,
});
const post = makePost({
create_at: baseTime + 1000,
props: aiProps,
});
expect(PostUtils.areConsecutivePostsBySameUser(post, previousPost)).toBe(false);
});
test('should return false when a normal post follows an AI post from the same user', () => {
const previousPost = makePost({
create_at: baseTime,
props: {
ai_generated_by: 'ai_user_id',
ai_generated_by_username: 'aibot',
},
});
const post = makePost({create_at: baseTime + 1000});
expect(PostUtils.areConsecutivePostsBySameUser(post, previousPost)).toBe(false);
});
});
describe('shouldShowActionsMenu', () => {
const baseState = {
entities: {
+4
View File
@@ -674,6 +674,10 @@ export function areConsecutivePostsBySameUser(post: Post, previousPost: Post): b
return false;
}
if (hasAiGeneratedMetadata(post)) {
return false;
}
const sameUser = post.user_id === previousPost.user_id;
const withinTimeWindow = post.create_at - previousPost.create_at <= Posts.POST_COLLAPSE_TIMEOUT;
const notFromWebhook = !(post.props && post.props.from_webhook) && !(previousPost.props && previousPost.props.from_webhook);