# Ticket 0.7 — Walking Skeleton

**Phase**: 0 — Project Scaffolding
**Priority**: Critical (blocking)
**Depends on**: 0.1, 0.2, 0.3, 0.4

## Description

Build a minimal end-to-end vertical slice that proves the entire pipeline works: spawn OpenCode, consume SSE events, translate them, relay via WebSocket, and render text in a browser. This is the **real proof of concept** — if this works, every subsequent ticket is incremental.

The walking skeleton intentionally cuts every corner. No markdown rendering, no tool UI, no permissions, no auth, no daemon. Just raw streamed text appearing on a web page. It validates the fundamental architectural bet: that we can bridge OpenCode's SSE events to a browser via WebSocket with acceptable latency and correctness.

### Why This Ticket Exists

Without a vertical slice, the first time all layers connect is deep into Phase 4. If the event model assumptions are wrong, or the SSE → WebSocket translation loses events, or the latency is unacceptable, we'd discover it after weeks of work on isolated components. This ticket surfaces those risks in days.

## Acceptance Criteria

### AC1: Spawns or connects to OpenCode server

```gherkin
Given OpenCode is installed and available on PATH
When the skeleton starts
Then it either spawns `opencode serve` or connects to an already-running instance
And it confirms the connection via health check (GET /global/health)
```

### AC2: Connects to SSE stream and receives events

```gherkin
Given the OpenCode server is running
When the skeleton connects to GET /event/
Then it receives the server.connected event
And it begins receiving events for active sessions
```

### AC3: Translates events to relay format

```gherkin
Given SSE events are streaming
When a message.part.delta event arrives with field "text"
Then the skeleton translates it to { type: "delta", text: "<content>" }

When a session.status event arrives with status "idle"
Then it translates to { type: "done", code: 0 }

When a session.status event arrives with status "busy"
Then it translates to { type: "status", status: "processing" }
```

### AC4: Serves a minimal HTML page

```gherkin
Given the skeleton is running
When a browser navigates to http://localhost:<port>
Then it receives an HTML page with:
  - A text display area (a <pre> or <div>)
  - A text input field
  - A send button
  - A WebSocket connection script
And the page connects to the relay's WebSocket endpoint
```

### AC5: Relays translated events to browser via WebSocket

```gherkin
Given a browser is connected via WebSocket
When translated events arrive from the SSE consumer
Then they are forwarded to the browser as JSON messages
And the browser renders delta text incrementally in the display area
And the user can see the agent's response appearing in real-time
```

### AC6: Sends user messages from browser to OpenCode

```gherkin
Given a browser is connected and a session exists
When the user types a message and clicks send
Then the relay forwards the message to OpenCode via POST /session/:sessionID/prompt_async
And the relay receives the agent's response via SSE
And the response streams to the browser in real-time
```

### AC7: Measures end-to-end latency

```gherkin
Given the skeleton is running with an active session
When an SSE event is received by the relay
Then it is forwarded to the browser within 50ms (local network)
And this is logged for validation
```

## What This Is NOT

- ❌ No markdown rendering — raw text only
- ❌ No tool call UI — tool events logged to console, not rendered
- ❌ No permission handling — use `opencode serve` with permissive config or approve via terminal
- ❌ No auth/PIN — localhost only, no security
- ❌ No daemon — foreground process only
- ❌ No session management UI — hardcode or auto-select first session
- ❌ No error recovery — crash = restart manually
- ❌ No tests — this is a spike/proof-of-concept (tests come with proper tickets)

## Testable Components

| Component | Test Type | What to Assert |
|-----------|-----------|----------------|
| Pipeline smoke test | Manual | Type message in browser → see streamed response |
| Latency check | Manual | Console log shows < 50ms relay overhead |
| SSE → WS mapping | Manual | Events arrive in browser, visible in DevTools Network tab |

## Implementation Notes

This ticket can borrow rough implementations from tickets 1.1–1.4, 2.1, 2.2 — but should be a **single-file-ish prototype**, not the production module structure. Think of it as a spike that validates the architecture. The production implementations in later tickets replace this code.

Suggested approach:
1. Single `src/skeleton.ts` file (or 2-3 small files)
2. Use native `fetch` for REST, `EventSource` (or fetch-based SSE) for streaming
3. Inline HTML string for the frontend (no Vite build needed)
4. `ws` library for WebSocket server
5. Run with `tsx src/skeleton.ts`

## Files

- `src/skeleton.ts` — main skeleton server (temporary, replaced by production modules)
- Or a small set: `src/skeleton/server.ts`, `src/skeleton/bridge.ts`, `src/skeleton/page.ts`

## Reference

- OpenCode health: `GET /global/health` → `{ healthy: true, version }`
- OpenCode SSE: `GET /event/` — server-sent events stream
- OpenCode send message: `POST /session/:id/prompt_async` — returns 204, response via SSE
- OpenCode sessions: `GET /session/` — list all sessions
- This ticket validates the architecture described in the implementation plan overview
