diff --git a/Components/ChatMessageList.razor b/Components/ChatMessageList.razor
new file mode 100644
index 0000000000..53afe92239
--- /dev/null
+++ b/Components/ChatMessageList.razor
@@ -0,0 +1,375 @@
+@using AutoPilot.App.Models
+@using Markdig
+
+@* Shared chat message list — used by both Home.razor (full) and Dashboard.razor (compact) *@
+
+
+ @if (!Messages.Any() && string.IsNullOrEmpty(StreamingContent))
+ {
+
@(Compact ? "No messages yet" : "Start a conversation with Copilot!")
+ }
+ else
+ {
+ @foreach (var msg in Messages)
+ {
+ @switch (msg.MessageType)
+ {
+ case ChatMessageType.User:
+
+ @if (!Compact)
+ {
+
+ }
+
+ @if (Compact)
+ {
+
You
+ }
+
@((MarkupString)FormatUserMessage(msg.Content))
+ @if (!Compact)
+ {
+
@msg.Timestamp.ToString("HH:mm")
+ }
+
+
+ break;
+
+ case ChatMessageType.Assistant:
+
+ @if (!Compact)
+ {
+
+ }
+
+ @if (Compact)
+ {
+
AI
+ }
+
@((MarkupString)RenderMarkdown(msg.Content))
+ @if (!Compact)
+ {
+
@msg.Timestamp.ToString("HH:mm")
+ }
+
+
+ break;
+
+ case ChatMessageType.Reasoning:
+ @if (Compact)
+ {
+
+
+
@(msg.IsComplete ? "Thought" : "Thinking...")
+
+ }
+ else
+ {
+
+
+ @if (!msg.IsCollapsed || LineCount(msg.Content) <= 5)
+ {
+
@msg.Content
+ }
+ else
+ {
+
@FirstLines(msg.Content, 3)
+ }
+
+ }
+ break;
+
+ case ChatMessageType.ToolCall:
+ @if (Compact)
+ {
+
+ }
+ else if (msg.ToolName == "task_complete")
+ {
+
+
+
@(string.IsNullOrEmpty(msg.Content) || IsUnusableResult(msg.Content) ? "Task complete" : msg.Content)
+
+ }
+ else
+ {
+
+ }
+ break;
+
+ case ChatMessageType.Error:
+
+ @if (Compact)
+ {
+
+
@msg.Content
+ }
+ else
+ {
+
+
@msg.Content
+ }
+
+ break;
+
+ case ChatMessageType.System:
+
+ break;
+ }
+ }
+
+ @* Current tool activity *@
+ @if (!string.IsNullOrEmpty(CurrentToolName))
+ {
+ @if (Compact)
+ {
+
+ }
+ else
+ {
+
+
+
+ }
+ }
+
+ @* Streaming content *@
+ @if (!string.IsNullOrEmpty(StreamingContent))
+ {
+
+ @if (!Compact)
+ {
+
+ }
+
+ @if (Compact)
+ {
+
AI
+ }
+
@((MarkupString)RenderMarkdown(StreamingContent))
+
+
+ }
+
+ @* Activity indicator *@
+ @if (IsProcessing && string.IsNullOrEmpty(StreamingContent) && string.IsNullOrEmpty(CurrentToolName))
+ {
+
+ @if (Compact)
+ {
+
AI
+
@(string.IsNullOrEmpty(ActivityText) ? "Thinking..." : ActivityText)
+ }
+ else
+ {
+
+
+
@(string.IsNullOrEmpty(ActivityText) ? "Thinking..." : ActivityText)
+
+ }
+
+ }
+ }
+
+
+@code {
+ [Parameter] public List