# Ticket 4.1 — Core Chat UI

**Phase**: 4 — Frontend UI
**Priority**: Critical (blocking)
**Depends on**: 2.2

## Description

The primary chat interface: message display, input field, streaming text rendering, code blocks, markdown, and auto-scroll. This is the main surface users interact with. Ported from claude-relay's `public/app.js` and modules with minimal changes.

## Acceptance Criteria

### AC1: Displays user messages

```gherkin
Given the chat UI is loaded
When a user message is in the history
Then it is displayed in a right-aligned bubble with the message text
And it shows the timestamp
And multi-line text preserves formatting
```

### AC2: Streams assistant responses in real-time

```gherkin
Given the agent is processing a request
When delta messages arrive via WebSocket
Then text appears character-by-character in the assistant message bubble
And the UI does not flicker or reflow during streaming
And the cursor/typing indicator is visible during streaming
```

### AC3: Renders markdown in assistant messages

```gherkin
Given an assistant message contains markdown
When it is rendered
Then headings (h1-h6) are styled appropriately
And bold, italic, strikethrough text is rendered
And links are clickable and open in new tabs
And lists (ordered and unordered) are properly formatted
And blockquotes are visually distinct
```

### AC4: Renders code blocks with syntax highlighting

```gherkin
Given an assistant message contains a fenced code block with a language tag
When it is rendered
Then the code block has syntax highlighting for that language
And a "Copy" button is visible on hover
And clicking "Copy" copies the code to the clipboard
And the language label is shown in the code block header
```

### AC5: Renders tables

```gherkin
Given an assistant message contains a markdown table
When it is rendered
Then it displays as a formatted HTML table
And columns are properly aligned
And the table is horizontally scrollable if too wide
```

### AC6: Renders Mermaid diagrams

```gherkin
Given an assistant message contains a ```mermaid code block
When it is rendered
Then it displays as an SVG diagram (not raw text)
And clicking the diagram opens it in a fullscreen modal
```

### AC7: Auto-scroll follows new content

```gherkin
Given the user is scrolled to the bottom of the chat
When new content arrives
Then the chat auto-scrolls to keep the latest content visible

Given the user has scrolled up to read earlier messages
When new content arrives
Then the chat does NOT auto-scroll (respects user's position)
And a "scroll to bottom" button appears
And clicking the button scrolls to the latest content
```

### AC8: Message input field

```gherkin
Given the chat UI is loaded
When the input field is focused
Then the user can type a message
And pressing Enter (or the send button) sends the message
And Shift+Enter inserts a newline
And the input field auto-grows for multi-line input
And the input field clears after sending
And the send button is disabled while the agent is processing
```

### AC9: Abort / stop button

```gherkin
Given the agent is processing a request
When the user clicks the "Stop" button (replaces Send button during processing)
Then it calls POST /session/{sessionID}/abort via the relay
And the button reverts to "Send" state
And the streaming response stops
And a "Generation stopped" indicator appears in the chat

Given the agent is idle
Then the Stop button is NOT visible (Send button shown instead)
```

### AC10: Cost and usage display

```gherkin
Given the agent finishes a response
When the result event includes cost and token data
Then the message footer shows:
  - Total cost (formatted as currency, e.g., "$0.03")
  - Input tokens / output tokens
  - Cache read / cache write tokens (if > 0)
  - Response duration
And the session header shows cumulative cost for the session
```

### AC11: Image attachment support

```gherkin
Given the input field is focused
When the user pastes an image from the clipboard
Then a thumbnail preview appears above the input
And the image is included in the message when sent
And the user can remove the image before sending

When the user drags and drops an image file
Then the same preview and attach behaviour applies
```

## Testable Components

| Component | Test Type | What to Assert |
|-----------|-----------|----------------|
| Markdown rendering | Unit | Headings, bold, italic, links, lists, blockquotes produce correct HTML |
| Code block rendering | Unit | Syntax highlighting applied, language label set |
| Table rendering | Unit | HTML table generated, scrollable wrapper added |
| Mermaid rendering | Unit | SVG generated from mermaid syntax |
| Message display | E2E | User and assistant messages render with correct styling and alignment |
| Streaming text | E2E | Delta text appears incrementally without flicker |
| Auto-scroll | E2E | Page scrolls during streaming; pauses when user scrolls up; scroll-to-bottom button appears |
| Input field | E2E | Send on Enter, newline on Shift+Enter, textarea auto-grows |
| Image paste | E2E | Pasting an image shows preview; image included in sent message |
| Abort button | E2E | Stop button visible during processing, calls abort, reverts to Send |
| Cost display | E2E | Token counts and cost shown after response completes |
| Send button state | E2E | Disabled during processing, enabled when idle |
| Full message round-trip | E2E | Type message → see processing indicator → see streamed response |
| Mobile layout | E2E | Chat UI is usable on mobile viewport (Pixel 7) |

### CSS Foundation

The chat UI establishes the core CSS architecture for the entire frontend:

- **CSS custom properties** (variables) for theming: `--color-primary`, `--color-bg`, `--color-text`, `--color-border`, `--color-code-bg`, etc.
- **Dark mode** by default (matching developer tool aesthetic), with optional light mode via `prefers-color-scheme`
- **Responsive breakpoints**: mobile-first, with breakpoints for tablet (768px) and desktop (1024px)
- **Typography scale**: consistent heading sizes, code font, line heights
- Base CSS file: `src/lib/public/styles/base.css` (imported by Vite)

## Files

- `src/lib/public/app.ts` — main browser application logic
- `src/lib/public/modules/markdown.ts` — markdown + mermaid rendering
- `src/lib/public/modules/state.ts` — client state management
- `src/lib/public/styles/base.css` — CSS foundation with custom properties

## Important: This Is a Rewrite, Not a Copy-Paste Port

The plan describes the frontend as "ported from claude-relay with minimal changes." In practice, this is closer to a **rewrite using claude-relay as reference**:

1. **Language change**: claude-relay's frontend is vanilla JavaScript (~1500 lines in app.js alone). This project uses TypeScript with Vite. Every file must be rewritten with proper types, not just renamed from `.js` to `.ts`.

2. **Event model differences**: claude-relay's frontend handles Claude Code's stream events directly. This frontend handles the *translated* event format from ticket 1.3. While the translator aims for compatibility, there will be edge cases — especially around tool lifecycle events (OpenCode's part-based model vs Claude Code's content-block model) and part types that have no Claude Code equivalent (`patch`, `agent`, `compaction`, `subtask`, `step-start`, `step-finish`).

3. **Implicit contracts**: claude-relay's JS code has no type annotations. Many contracts between modules are implicit (e.g., what shape does a tool message have? what fields does the sidebar expect?). These must be made explicit during the TypeScript rewrite.

4. **Time estimation**: Allow significantly more time than a "port" suggests. Each ticket in Phase 4 involves reading the old JS, understanding its implicit contracts, rewriting in TypeScript with proper types, and adapting to the translated event format.

5. **Validation strategy**: Use the snapshot tests from ticket 1.3 (AC16) as the contract. The frontend should be built to consume the translator's snapshot output format. If the translator output and the frontend expectations diverge, snapshot tests catch it.

## Reference

- Claude-relay app.js: `../claude-relay/lib/public/app.js`
- Claude-relay markdown: `../claude-relay/lib/public/modules/markdown.js`
