# Ticket 2.1 — HTTP Server

**Phase**: 2 — Server & WebSocket Layer
**Priority**: Critical (blocking)
**Depends on**: 0.2

## Description

The main HTTP/HTTPS server that serves the web UI, handles authentication, routes requests to the correct project, and upgrades WebSocket connections. Adapted from claude-relay's `server.js` with modifications for OpenCode.

## Acceptance Criteria

### AC1: HTTPS server starts with self-signed certificates

```gherkin
Given mkcert is installed on the system
When the server starts for the first time
Then it generates a self-signed certificate and key via mkcert
And it stores them in the config directory (~/.conduit/)
And it starts an HTTPS server on the configured port (default: 2633)
And it logs the local and network URLs
```

### AC2: Falls back to HTTP when mkcert is unavailable

```gherkin
Given mkcert is NOT installed
When the server starts
Then it starts an HTTP server instead of HTTPS
And it logs a warning recommending mkcert for push notifications and PWA
```

### AC3: Serves static frontend assets

```gherkin
Given the server is running
When a browser requests /p/{slug}/
Then it returns the main HTML page
When a browser requests /p/{slug}/app.js
Then it returns the JavaScript file with correct Content-Type
When a browser requests /p/{slug}/modules/{file}
Then it returns the module file with correct Content-Type
```

### AC4: Multi-project routing via slugs

```gherkin
Given projects "my-app" and "api-server" are registered
When a browser requests /p/my-app/
Then it receives the UI scoped to the "my-app" project
When a browser requests /p/api-server/
Then it receives the UI scoped to the "api-server" project
When a browser requests /p/nonexistent/
Then it returns 404 with a helpful message
```

### AC5: Dashboard route

```gherkin
Given a single project is registered
When a browser requests /
Then it redirects to /p/{slug}/

Given multiple projects are registered
When a browser requests /
Then it shows a project picker listing all registered projects
And each project links to /p/{slug}/
```

### AC6: Server binds to all interfaces

```gherkin
Given the server starts
When it binds
Then it listens on 0.0.0.0 (all network interfaces)
And it is accessible from other devices on the same network
And it logs both the local URL and the LAN URL
```

## Testable Components

| Component | Test Type | What to Assert |
|-----------|-----------|----------------|
| `startServer(config)` | Unit | Correct port, cert paths, binding address |
| TLS certificate generation | Fixture | mkcert called, certs stored, server starts HTTPS |
| HTTP fallback | Unit | Server starts HTTP when certs unavailable |
| Static file serving | Fixture | Correct files returned with correct MIME types |
| Slug routing | Unit | Requests routed to correct project |
| Dashboard redirect | Unit | Single project → redirect; multiple → picker |
| 404 handling | Unit | Unknown slugs return helpful 404 |
| Network binding | Fixture | Accessible from another device (or mock) |

## API Shape

```ts
// src/lib/server.ts
export class RelayServer {
  constructor(options = {})     // { port, certDir, projects }
  async start()                 // Start HTTP/HTTPS server
  async stop()                  // Graceful shutdown
  addProject(slug, project)     // Register a project for routing
  removeProject(slug)           // Unregister a project
  getUrl()                      // Returns { local, network }
}
```

## Reference

- Claude-relay server: `../claude-relay/lib/server.js`
- Port default 2633 matches claude-relay
- mkcert integration: same pattern as claude-relay
