diff --git a/src/renderer/components/team/messages/MessagesPanel.tsx b/src/renderer/components/team/messages/MessagesPanel.tsx index 9454ef65..d8407004 100644 --- a/src/renderer/components/team/messages/MessagesPanel.tsx +++ b/src/renderer/components/team/messages/MessagesPanel.tsx @@ -733,12 +733,21 @@ export const MessagesPanel = memo(function MessagesPanel({ }, []); const handleReviseMessage = useCallback( - (message: InboxMessage) => { + async (message: InboxMessage) => { if (!isRevisableUserSentMessage(message, memberNames)) return; const originalMessageId = trimString(message.messageId); if (originalMessageId !== revisionMessageId) return; const recipient = trimString(message.to); const originalText = getRevisableMessageText(message); + try { + await sendTeamMessage(teamName, { + member: recipient, + text: buildRevisionNoticeText(originalMessageId, originalText), + summary: `${REVISION_NOTICE_PREFIX} ${originalMessageId}`, + }); + } catch { + return; + } setRevisionRequest({ requestId: `${originalMessageId}:${Date.now()}`, originalMessageId, @@ -747,11 +756,6 @@ export const MessagesPanel = memo(function MessagesPanel({ actionMode: message.actionMode, }); composerTextareaRef.current?.focus(); - void sendTeamMessage(teamName, { - member: recipient, - text: buildRevisionNoticeText(originalMessageId, originalText), - summary: `${REVISION_NOTICE_PREFIX} ${originalMessageId}`, - }).catch(() => undefined); }, [memberNames, revisionMessageId, sendTeamMessage, teamName] ); diff --git a/test/renderer/components/team/messages/MessagesPanel.test.ts b/test/renderer/components/team/messages/MessagesPanel.test.ts index b1b1a0be..c9af6a97 100644 --- a/test/renderer/components/team/messages/MessagesPanel.test.ts +++ b/test/renderer/components/team/messages/MessagesPanel.test.ts @@ -868,6 +868,80 @@ describe('MessagesPanel idle summary invariants', () => { }); }); + it('does not enter revision mode when the revision notice fails to send', async () => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); + storeState.sendTeamMessage.mockRejectedValueOnce(new Error('send failed')); + const host = document.createElement('div'); + document.body.appendChild(host); + const root = createRoot(host); + + await act(async () => { + storeState.teamMessagesByName['atlas-hq'] = { + canonicalMessages: [ + makeMessage({ + messageId: 'latest-user-send', + from: 'user', + to: 'bob', + source: 'user_sent', + timestamp: '2026-04-08T12:05:00.000Z', + text: 'raw transport text', + summary: 'restore this text', + }), + ], + optimisticMessages: [], + feedRevision: 'rev-1', + nextCursor: null, + hasMore: false, + lastFetchedAt: Date.now(), + loadingHead: false, + loadingOlder: false, + headHydrated: true, + }; + root.render( + React.createElement(MessagesPanel, { + teamName: 'atlas-hq', + position: 'sidebar', + onPositionChange: vi.fn(), + members: [ + { + agentType: 'developer', + currentTaskId: null, + lastActiveAt: null, + messageCount: 0, + name: 'bob', + role: 'Developer', + status: 'idle', + taskCount: 0, + }, + ], + tasks: [], + timeWindow: null, + pendingRepliesByMember: {}, + onPendingReplyChange: vi.fn(), + }) + ); + await Promise.resolve(); + }); + + const editButtons = Array.from(host.querySelectorAll('button')).filter( + (button) => button.textContent === 'Edit message' + ); + expect(editButtons).toHaveLength(1); + + await act(async () => { + editButtons[0].click(); + await Promise.resolve(); + }); + + expect(storeState.sendTeamMessage).toHaveBeenCalledOnce(); + expect(host.textContent).not.toContain('composer revision:latest-user-send:restore this text'); + + await act(async () => { + root.unmount(); + await Promise.resolve(); + }); + }); + it('clears stale OpenCode runtime diagnostics once the member reply is visible', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); const host = document.createElement('div');