`) while `isLoading`
+- Infinite scroll sentinel: `` at bottom of list
+
+### ChatMessageList (main chat area)
+- Background: `bg-background`
+- Padding: `p-4` with `gap-4` between messages
+- User message: right-aligned, `bg-secondary text-secondary-foreground`, `rounded-none` (matches `--radius: 0` global setting), `max-w-[75%]`, padding `px-4 py-2`
+- Assistant message: left-aligned, no background (transparent), `max-w-[85%]`
+- Message timestamp: Meta, visible on hover only (`opacity-0 group-hover:opacity-100 transition-opacity`)
+- Agent label on assistant messages: not in Phase 21 (agent identity lands in Phase 22) — omit avatar/name row entirely
+
+### ChatMarkdownMessage (assistant message renderer)
+- Extends `MarkdownBody` with `rehype-highlight` added to `rehypePlugins`
+- Code block container: `bg-card border border-border`, `relative` positioning for copy button
+- Code block language label: Meta (12px), `text-muted-foreground`, top-left inside block (`absolute top-2 left-3`)
+- Copy button: icon-only (Copy from lucide, 14px), `absolute top-1.5 right-1.5`, `variant="ghost" size="icon-sm"`, transitions to Check icon for 2 seconds on success
+- Copy button accessible label: `aria-label="Copy code"`
+
+### ChatInput (bottom of ChatPanel)
+- Component: `