**1. Create `ui/src/components/ChatMessageIdentityBar.tsx`:**
```typescript
import { AgentIcon } from "./AgentIconPicker";
import { agentRoleColors, agentRoleColorDefault } from "../lib/agent-role-colors";
import type { AgentRole } from "@paperclipai/shared";
interface ChatMessageIdentityBarProps {
agentName: string;
agentIcon?: string | null;
agentRole?: AgentRole | null;
timestamp?: string;
isStreaming?: boolean;
}
export function ChatMessageIdentityBar({
agentName,
agentIcon,
agentRole,
timestamp,
isStreaming,
}: ChatMessageIdentityBarProps) {
const colorClass = agentRole ? (agentRoleColors[agentRole] ?? agentRoleColorDefault) : agentRoleColorDefault;
return (
{agentName}
{isStreaming && (
)}
{timestamp && (
{new Date(timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
)}
);
}
```
Per UI spec: icon 16x16 (`h-4 w-4`), name 13px semibold, timestamp 11px muted, streaming dot uses `bg-cyan-400 animate-pulse` from `agentStatusDot.running`.
**2. Create `ui/src/components/ChatStreamingCursor.tsx`:**
```typescript
export function ChatStreamingCursor() {
return (
);
}
```
Per UI spec: `w-2 h-[1em] bg-foreground/70 animate-cursor-blink`, `aria-hidden="true"` (decorative only).
**3. Extend `ChatMessage` props and rendering in `ui/src/components/ChatMessage.tsx`:**
Update the `ChatMessageProps` interface to:
```typescript
interface ChatMessageProps {
role: "user" | "assistant" | "system";
content: string;
agentName?: string | null;
agentIcon?: string | null;
agentRole?: AgentRole | null;
timestamp?: string;
isStreaming?: boolean;
}
```
Import `AgentRole` from `@paperclipai/shared`, `ChatMessageIdentityBar`, and `ChatStreamingCursor`.
Update the assistant/system rendering branch to:
```tsx
return (
{agentName && (
)}
{isStreaming && }
);
```
The `group` class enables hover-reveal for edit/retry buttons (Plan 03). User message branch remains unchanged for now (edit action is Plan 03).
**4. Replace test stubs in `ui/src/components/ChatMessageIdentityBar.test.tsx`** with real tests:
```typescript
// @vitest-environment jsdom
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { ChatMessageIdentityBar } from "./ChatMessageIdentityBar";
describe("ChatMessageIdentityBar", () => {
it("renders agent name in semibold text", () => {
render();
expect(screen.getByText("PM Agent")).toBeDefined();
});
it("renders timestamp when provided", () => {
render();
// Should contain formatted time
const el = screen.getByText(/12:30/);
expect(el).toBeDefined();
});
it("applies role-specific color class", () => {
const { container } = render(
);
const nameEl = container.querySelector(".font-semibold");
expect(nameEl?.className).toContain("text-blue-600");
expect(nameEl?.className).toContain("dark:text-blue-400");
});
it("shows streaming indicator dot when isStreaming", () => {
const { container } = render(
);
const dot = container.querySelector(".animate-pulse");
expect(dot).toBeDefined();
});
});
```
If `@testing-library/react` is not installed, use `createRoot` + `container.querySelector` pattern from `ChatInput.test.tsx`.