# Task 007: Message Display ## Goal Create the message display component that renders user and assistant messages in a scrollable stream, showing message content, tool calls, and streaming states. > Note: This legacy task predates `message-stream-v2` and the normalized message store; the new implementation lives under `packages/ui/src/components/message-stream-v2.tsx`. ## Prerequisites - Task 006 completed (Tab navigation in place) - Understanding of message part structure from OpenCode SDK - Familiarity with markdown rendering - Knowledge of SolidJS For/Show components ## Acceptance Criteria - [ ] Messages render in chronological order - [ ] User messages display with correct styling - [ ] Assistant messages display with agent label - [ ] Text content renders properly - [ ] Tool calls display inline with collapse/expand - [ ] Auto-scroll to bottom on new messages - [ ] Manual scroll up disables auto-scroll - [ ] "Scroll to bottom" button appears when scrolled up - [ ] Empty state shows when no messages - [ ] Loading state shows when fetching messages - [ ] Timestamps display for each message - [ ] Messages are accessible and keyboard-navigable ## Steps ### 1. Define Message Types **src/types/message.ts:** ```typescript export interface Message { id: string sessionId: string type: "user" | "assistant" parts: MessagePart[] timestamp: number status: "sending" | "sent" | "streaming" | "complete" | "error" } export type MessagePart = TextPart | ToolCallPart | ToolResultPart | ErrorPart export interface TextPart { type: "text" text: string } export interface ToolCallPart { type: "tool_call" id: string tool: string input: any status: "pending" | "running" | "success" | "error" } export interface ToolResultPart { type: "tool_result" toolCallId: string output: any error?: string } export interface ErrorPart { type: "error" message: string } ``` ### 2. Create Message Stream Component **src/components/message-stream.tsx:** ```typescript import { For, Show, createSignal, onMount, onCleanup } from "solid-js" import { Message } from "../types/message" import MessageItem from "./message-item" interface MessageStreamProps { sessionId: string messages: Message[] loading?: boolean } export default function MessageStream(props: MessageStreamProps) { let containerRef: HTMLDivElement | undefined const [autoScroll, setAutoScroll] = createSignal(true) const [showScrollButton, setShowScrollButton] = createSignal(false) function scrollToBottom() { if (containerRef) { containerRef.scrollTop = containerRef.scrollHeight setAutoScroll(true) setShowScrollButton(false) } } function handleScroll() { if (!containerRef) return const { scrollTop, scrollHeight, clientHeight } = containerRef const isAtBottom = scrollHeight - scrollTop - clientHeight < 50 setAutoScroll(isAtBottom) setShowScrollButton(!isAtBottom) } onMount(() => { if (autoScroll()) { scrollToBottom() } }) // Auto-scroll when new messages arrive const messagesLength = () => props.messages.length createEffect(() => { messagesLength() // Track changes if (autoScroll()) { setTimeout(scrollToBottom, 0) } }) return (
) } ``` ### 3. Create Message Item Component **src/components/message-item.tsx:** ```typescript import { For, Show } from "solid-js" import { Message } from "../types/message" import MessagePart from "./message-part" interface MessageItemProps { message: Message } export default function MessageItem(props: MessageItemProps) { const isUser = () => props.message.type === "user" const timestamp = () => { const date = new Date(props.message.timestamp) return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) } return ( ) } ``` ### 4. Create Message Part Component **src/components/message-part.tsx:** ```typescript import { Show, Match, Switch } from "solid-js" import { MessagePart as MessagePartType } from "../types/message" import ToolCall from "./tool-call" interface MessagePartProps { part: MessagePartType } export default function MessagePart(props: MessagePartProps) { return ({JSON.stringify(props.toolCall.input, null, 2)}
{formatToolOutput()}