+
{message.pending && message.content && (
diff --git a/src/renderer/lib/messagePhase.ts b/src/renderer/lib/messagePhase.ts
new file mode 100644
index 0000000..eab5306
--- /dev/null
+++ b/src/renderer/lib/messagePhase.ts
@@ -0,0 +1,35 @@
+import type { ChatMessageRecord, SessionRecord } from '@shared/domain/session';
+
+export type AssistantMessagePhase = 'default' | 'thinking' | 'final';
+
+export function getAssistantMessagePhase(
+ session: SessionRecord,
+ message: ChatMessageRecord,
+ index: number,
+): AssistantMessagePhase {
+ if (message.role !== 'assistant') {
+ return 'default';
+ }
+
+ if (message.pending) {
+ return 'thinking';
+ }
+
+ if (session.status === 'running') {
+ return 'default';
+ }
+
+ const lastCompletedAssistantIndex = findLastCompletedAssistantIndex(session.messages);
+ return index === lastCompletedAssistantIndex ? 'final' : 'default';
+}
+
+function findLastCompletedAssistantIndex(messages: ChatMessageRecord[]): number {
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
+ const message = messages[index];
+ if (message.role === 'assistant' && !message.pending) {
+ return index;
+ }
+ }
+
+ return -1;
+}
diff --git a/tests/renderer/messagePhase.test.ts b/tests/renderer/messagePhase.test.ts
new file mode 100644
index 0000000..90cdaae
--- /dev/null
+++ b/tests/renderer/messagePhase.test.ts
@@ -0,0 +1,87 @@
+import { describe, expect, test } from 'bun:test';
+
+import { getAssistantMessagePhase } from '@renderer/lib/messagePhase';
+import type { SessionRecord } from '@shared/domain/session';
+
+function createSession(
+ messages: SessionRecord['messages'],
+ status: SessionRecord['status'] = 'idle',
+): SessionRecord {
+ return {
+ id: 'session-1',
+ projectId: 'project-1',
+ patternId: 'pattern-1',
+ title: 'Test session',
+ createdAt: '2026-03-23T00:00:00.000Z',
+ updatedAt: '2026-03-23T00:00:00.000Z',
+ status,
+ messages,
+ };
+}
+
+describe('assistant message phase', () => {
+ test('marks pending assistant messages as thinking', () => {
+ const session = createSession([
+ {
+ id: 'msg-1',
+ role: 'assistant',
+ authorName: 'Triage',
+ content: 'Draft',
+ createdAt: '2026-03-23T00:00:00.000Z',
+ pending: true,
+ },
+ ], 'running');
+
+ expect(getAssistantMessagePhase(session, session.messages[0], 0)).toBe('thinking');
+ });
+
+ test('marks the last completed assistant message as final when the session is idle', () => {
+ const session = createSession([
+ {
+ id: 'msg-1',
+ role: 'assistant',
+ authorName: 'Triage',
+ content: 'Earlier',
+ createdAt: '2026-03-23T00:00:00.000Z',
+ },
+ {
+ id: 'msg-2',
+ role: 'assistant',
+ authorName: 'UX Specialist',
+ content: 'Final answer',
+ createdAt: '2026-03-23T00:00:01.000Z',
+ },
+ ]);
+
+ expect(getAssistantMessagePhase(session, session.messages[0], 0)).toBe('default');
+ expect(getAssistantMessagePhase(session, session.messages[1], 1)).toBe('final');
+ });
+
+ test('does not mark completed assistant messages as final while the session is still running', () => {
+ const session = createSession([
+ {
+ id: 'msg-1',
+ role: 'assistant',
+ authorName: 'Triage',
+ content: 'In progress',
+ createdAt: '2026-03-23T00:00:00.000Z',
+ },
+ ], 'running');
+
+ expect(getAssistantMessagePhase(session, session.messages[0], 0)).toBe('default');
+ });
+
+ test('ignores non-assistant messages', () => {
+ const session = createSession([
+ {
+ id: 'msg-1',
+ role: 'user',
+ authorName: 'You',
+ content: 'Hello',
+ createdAt: '2026-03-23T00:00:00.000Z',
+ },
+ ]);
+
+ expect(getAssistantMessagePhase(session, session.messages[0], 0)).toBe('default');
+ });
+});