Files
spiceflow/PRD.md
Adam Jeniski 66f072a5e6 Add comprehensive PRD and terminal UX improvements
- Add PRD.md with detailed product requirements documentation
- Unify tmux screen size presets to consistent 24-row height
- Add Ctrl+Down keyboard shortcut to scroll terminal to bottom

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 18:04:26 -05:00

1425 lines
45 KiB
Markdown

# Spiceflow Product Requirements Document
## Executive Summary
Spiceflow is an AI Session Orchestration Progressive Web App (PWA) for monitoring and interacting with Claude Code and OpenCode CLI sessions from mobile devices or web browsers. It provides a unified interface to manage multiple AI coding assistant sessions, handle permission requests, and interact with tmux terminal sessions.
---
## Table of Contents
1. [Product Overview](#1-product-overview)
2. [System Architecture](#2-system-architecture)
3. [Data Models](#3-data-models)
4. [Session Management](#4-session-management)
5. [Messaging System](#5-messaging-system)
6. [Permission System](#6-permission-system)
7. [Terminal Integration](#7-terminal-integration)
8. [Push Notifications](#8-push-notifications)
9. [API Specification](#9-api-specification)
10. [WebSocket Protocol](#10-websocket-protocol)
11. [User Interface](#11-user-interface)
12. [User Flows](#12-user-flows)
13. [Security Considerations](#13-security-considerations)
14. [Performance Requirements](#14-performance-requirements)
---
## 1. Product Overview
### 1.1 Purpose
Spiceflow bridges the gap between AI coding assistants (Claude Code, OpenCode) and mobile/web interfaces, enabling developers to:
- Monitor AI coding sessions from any device
- Respond to permission requests remotely
- Manage multiple concurrent sessions
- Interact with shell terminals via tmux
### 1.2 Supported Providers
| Provider | Type | Interaction Model | Permission Handling |
|----------|------|-------------------|---------------------|
| Claude Code | AI Coding Assistant | Chat-based with streaming | Manual or Auto-accept |
| OpenCode | AI Coding Assistant | Chat-based with streaming | Auto-approved by CLI |
| tmux | Terminal Multiplexer | Direct shell access | N/A |
### 1.3 Key Features
- **Real-time Streaming**: Live display of AI responses via WebSocket
- **Permission Interception**: Approve, deny, or redirect file/command operations
- **Auto-Accept Edits**: Automatic approval of Write/Edit operations for Claude
- **Terminal Emulation**: Full tmux session control with keyboard input
- **Push Notifications**: Web push alerts for pending permissions
- **PWA Support**: Installable app with offline capability
---
## 2. System Architecture
### 2.1 High-Level Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ PWA Client (SvelteKit) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Sessions │ │ Messages │ │ Terminal │ │ Push Notif Store │ │
│ │ Store │ │ Store │ │ View │ │ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │
│ │ │ │ │ │
│ └─────────────┴─────────────┴──────────────────┘ │
│ │ │
│ ┌─────────┴─────────┐ │
│ │ API Client │ │
│ │ WebSocket │ │
│ └─────────┬─────────┘ │
└──────────────────────────────┼───────────────────────────────────┘
│ HTTP / WebSocket
┌──────────────────────────────┼───────────────────────────────────┐
│ Spiceflow Server (Clojure) │
│ ┌─────────────────┬─────────┴─────────┬─────────────────────┐ │
│ │ HTTP Routes │ WebSocket │ Push Sender │ │
│ │ (Reitit) │ Manager │ (RFC 8291) │ │
│ └────────┬────────┴─────────┬─────────┴──────────┬──────────┘ │
│ │ │ │ │
│ ┌────────┴──────────────────┴────────────────────┴──────────┐ │
│ │ Session Manager │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ Active Processes (ConcurrentHashMap) │ │ │
│ │ │ session-id → {process, stdin, stdout, stderr} │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────┬──────────────────┬────────────────────┬──────────┘ │
│ │ │ │ │
│ ┌────────┴────────┐ ┌───────┴───────┐ ┌─────────┴─────────┐ │
│ │ Claude Adapter │ │OpenCode Adapt.│ │ Tmux Adapter │ │
│ │ (JSONL stream) │ │(JSON stream) │ │ (raw terminal) │ │
│ └────────┬────────┘ └───────┬───────┘ └─────────┬─────────┘ │
│ │ │ │ │
│ ┌────────┴──────────────────┴───────────────────┴──────────┐ │
│ │ DataStore (SQLite) │ │
│ │ sessions | messages | push_subscriptions | vapid_keys │ │
│ └───────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────┼──────────────────────┐
│ │ │
┌───────┴───────┐ ┌───────┴───────┐ ┌───────┴───────┐
│ Claude Code │ │ OpenCode │ │ tmux │
│ CLI │ │ CLI │ │ sessions │
└───────────────┘ └───────────────┘ └───────────────┘
```
### 2.2 Technology Stack
| Layer | Technology | Purpose |
|-------|------------|---------|
| Frontend | SvelteKit 2.5, Svelte 4 | PWA framework |
| Styling | Tailwind CSS | Utility-first CSS |
| Build | Vite, vite-plugin-pwa | Asset bundling, service worker |
| Backend | Clojure 1.11, Ring/Jetty | HTTP server |
| Routing | Reitit | REST API routing |
| Database | SQLite via next.jdbc | Persistent storage |
| State | Mount | Component lifecycle |
| Testing | Kaocha (unit), Playwright (E2E) | Test frameworks |
### 2.3 Data Flow
```
User Input → HTTP POST → Session Manager → Adapter → CLI stdin
CLI stdout → Adapter.read-stream → Session Manager ─────┤
│ │
┌──────────┴──────────┐ │
│ │ │
Save to DB WebSocket Broadcast
│ │
└──────────┬──────────┘
PWA Client
```
---
## 3. Data Models
### 3.1 Session
```typescript
interface Session {
id: string; // UUID (DB sessions) or tmux name
provider: 'claude' | 'opencode' | 'tmux';
external_id?: string; // CLI-specific session identifier
title?: string; // User-friendly name
working_dir?: string; // Current working directory
spawn_dir?: string; // Original spawn directory
status: 'idle' | 'processing' | 'awaiting-permission';
pending_permission?: PermissionRequest; // JSON blob
auto_accept_edits?: boolean; // Auto-approve Write/Edit (Claude only)
created_at: string; // ISO timestamp
updated_at: string; // ISO timestamp
}
```
**Status State Machine:**
```
┌─────────────────────────────────┐
│ │
▼ │
┌──────┐ send_message ┌────────┴───┐
│ idle │ ─────────────────▶ │ processing │
└──────┘ └─────┬──────┘
▲ │
│ │
│ stream_ends │ permission_denied
│ (no pending) │
│ ▼
│ ┌─────────────────────┐
│ │ awaiting-permission │
│ └──────────┬──────────┘
│ │
│ respond_to_permission │
└───────────────────────────────┘
```
### 3.2 Message
```typescript
interface Message {
id: string; // UUID
session_id: string; // FK to sessions
role: 'user' | 'assistant' | 'system';
content: string; // Message text
metadata?: {
type?: 'permission-request'; // Permission message
status?: 'accept' | 'deny' | 'steer'; // Permission response
auto_accepted?: boolean; // Was auto-approved
denials?: PermissionDenial[]; // Tool requests
message_id?: string; // For permission tracking
};
created_at: string; // ISO timestamp
}
```
### 3.3 Permission Request
```typescript
interface PermissionRequest {
tools: string[]; // ['Write', 'Edit', 'Bash', etc.]
denials: PermissionDenial[]; // Detailed tool requests
}
interface PermissionDenial {
tool: string; // Tool name
input: ToolInput; // Tool-specific input
description: string; // Human-readable description
}
// Tool Input Types
interface WriteToolInput {
file_path: string;
content: string;
}
interface EditToolInput {
file_path: string;
old_string: string;
new_string: string;
}
interface BashToolInput {
command: string;
}
```
### 3.4 Push Subscription
```typescript
interface PushSubscription {
id: string; // UUID
endpoint: string; // Push service URL (unique)
p256dh: string; // Client ECDH public key (base64url)
auth: string; // Client auth secret (base64url)
user_agent?: string; // Browser info
created_at: string; // ISO timestamp
}
```
### 3.5 VAPID Keys
```typescript
interface VapidKeys {
public_key: string; // EC P-256 uncompressed point (base64url)
private_key: string; // Raw scalar (base64url)
}
```
---
## 4. Session Management
### 4.1 Session Lifecycle
#### 4.1.1 Creation
**Claude/OpenCode:**
1. Client POSTs to `/api/sessions` with `{provider: 'claude'|'opencode'}`
2. Server generates UUID, saves to database
3. Returns session with `status: 'idle'`
**Tmux:**
1. Client POSTs to `/api/sessions` with `{provider: 'tmux'}`
2. Server generates readable name: `spiceflow-{adjective}-{noun}-{4digits}`
3. Creates tmux session via `tmux new-session -d -s {name}`
4. Sets up output capture via `pipe-pane`
5. Returns session (ID = tmux session name)
#### 4.1.2 Message Sending
```
send_message_to_session(session_id, message)
├─▶ Save user message to database
├─▶ Session not running?
│ └─▶ start_session(session_id)
│ ├─▶ Get adapter for provider
│ ├─▶ adapter.spawn_session(session_id, opts)
│ └─▶ Store handle in active_processes map
├─▶ adapter.send_message(handle, message)
└─▶ Return handle for streaming
```
#### 4.1.3 Response Streaming
```
stream_session_response(session_id, callback)
├─▶ Get session and active handle
├─▶ adapter.read_stream(handle, event_callback)
│ │
│ ├─▶ On 'init': Extract external_id, spawn_dir
│ │
│ ├─▶ On 'content-delta':
│ │ ├─▶ Accumulate text
│ │ └─▶ Broadcast via WebSocket
│ │
│ ├─▶ On 'result':
│ │ ├─▶ Save assistant message
│ │ ├─▶ Check for permission_denials
│ │ │ ├─▶ If auto-accept eligible:
│ │ │ │ ├─▶ Mark message status='accept'
│ │ │ │ ├─▶ Broadcast with auto_accepted=true
│ │ │ │ └─▶ Recurse after responding
│ │ │ └─▶ Else:
│ │ │ ├─▶ Set pending_permission
│ │ │ ├─▶ Broadcast permission-request
│ │ │ └─▶ Schedule push notification (15s)
│ │ └─▶ Extract working_dir from tool results
│ │
│ └─▶ On 'message-stop': Finalize
└─▶ On stream end:
├─▶ If auto-accepted: respond_to_permission(:accept)
├─▶ Update session status
└─▶ Remove from active_processes
```
#### 4.1.4 Session Discovery
**Claude Code:**
- Scans `~/.claude/projects/*/sessions/*.json`
- Decodes URL-encoded paths from filenames
- Returns list of discovered sessions with external IDs
**Tmux:**
- Runs `tmux list-sessions -F "#{session_name}:#{pane_current_path}"`
- Filters for `spiceflow-*` prefix
- Returns managed sessions only
### 4.2 Process Handle Structure
```clojure
{:process java.lang.Process ; JVM process reference
:stdin java.io.BufferedWriter ; Write messages here
:stdout java.io.BufferedReader ; Read JSONL/output here
:stderr java.io.BufferedReader ; Error stream
;; Tmux-specific:
:session-name string ; Tmux session name
:output-file string ; Pipe-pane log path
:end-marker string ; Command completion marker
:marker-id string ; UUID for marker
:original-cmd string ; User's command (for filtering)
}
```
### 4.3 Adapter Protocol
```clojure
(defprotocol AgentAdapter
(provider-name [this])
;; Returns :claude, :opencode, or :tmux
(discover-sessions [this])
;; Returns [{:external-id :working-dir :title}]
(spawn-session [this session-id opts])
;; Creates CLI process, returns handle
(send-message [this handle message])
;; Writes to stdin, returns handle (possibly updated)
(read-stream [this handle callback])
;; Reads output, calls callback(event) for each
(kill-process [this handle])
;; Terminates process
(parse-output [this line]))
;; Parses output line to event map
```
---
## 5. Messaging System
### 5.1 Message Flow
```
┌─────────┐ POST /send ┌─────────┐ stdin ┌─────────┐
│ Client │ ──────────────────▶ │ Server │ ──────────────▶ │ CLI │
└─────────┘ └─────────┘ └────┬────┘
▲ │ │
│ │ │ stdout
│ Save user │ (JSONL)
│ message │
│ │ ▼
│ │ ┌─────────┐
│ WebSocket ┌┴┐ │ Adapter │
│ content-delta ◀───┤ │◀───────────────────│ Parse │
│ message-stop └┬┘ └─────────┘
│ permission-request │
│ │
│ Save assistant
│ message
└───────────────────────────────┘
```
### 5.2 Stream Event Types
| Event | Source | Payload | Purpose |
|-------|--------|---------|---------|
| `init` | CLI startup | `{session-id, cwd}` | Session initialized |
| `content-delta` | Assistant response | `{text}` | Incremental text |
| `message-stop` | Response complete | `{}` | Stream finalized |
| `permission-request` | Permission denied | `{permission-request, message-id, message}` | Tool blocked |
| `working-dir-update` | Directory change | `{working-dir}` | CWD updated |
| `terminal-update` | Tmux output | `{content, diff}` | Terminal changed |
| `error` | Any failure | `{message}` | Error occurred |
### 5.3 JSONL Parsing (Claude)
```json
{"type":"system","subtype":"init","session_id":"abc","cwd":"/home/user"}
{"type":"assistant","message":{"content":[{"type":"text","text":"Hello"}]}}
{"type":"result","result":"success","permission_denials":[...]}
```
**Event Mapping:**
- `system.init``:init`
- `assistant``:content-delta` (aggregated)
- `result``:result` with permission extraction
### 5.4 JSON Parsing (OpenCode)
```json
{"type":"step_start","sessionID":"abc","cwd":"/home/user"}
{"type":"text","content":"Hello"}
{"type":"step_finish","reason":"complete"}
```
**Event Mapping:**
- `step_start``:init`
- `text``:content-delta`
- `step_finish``:result`
---
## 6. Permission System
### 6.1 Permission Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ Permission Request Flow │
└─────────────────────────────────────────────────────────────────┘
CLI requests tool ──▶ Server detects denial ──▶ Check auto-accept
┌────────────────────────────┴────────┐
│ │
Auto-accept? Manual
│ │
┌─────┴─────┐ │
│ │ │
Only Write/ Other │
Edit tools tools │
│ │ │
▼ │ │
Auto-accept └───────────────────────────────┤
immediately │
│ │
│ ▼
│ ┌───────────────────┐
│ │ Set pending_perm │
│ │ Broadcast to WS │
│ │ Schedule push │
│ └─────────┬─────────┘
│ │
│ ▼
│ ┌───────────────────┐
│ │ Client displays │
│ │ permission UI │
│ └─────────┬─────────┘
│ │
│ User responds: Accept/Deny/Steer
│ │
│ ▼
▼ ┌───────────────────┐
┌───────────────────┐ │ POST /permission │
│ respond_to_perm │◀────────────────│ {response, msg} │
│ :accept, nil │ └───────────────────┘
└─────────┬─────────┘
┌───────────────────┐
│ Spawn new process │
│ --resume │
│ --allowedTools │
└─────────┬─────────┘
┌───────────────────┐
│ Send message: │
│ "continue" (accept)│
│ "denied" (deny) │
│ user text (steer) │
└─────────┬─────────┘
┌───────────────────┐
│ Stream new response│
└───────────────────┘
```
### 6.2 Auto-Accept Logic
```clojure
(defn should-auto-accept? [session perm-req]
(and (:auto-accept-edits session)
(every? #{"Write" "Edit"} (:tools perm-req))))
```
**Eligible Tools:**
- `Write` - File creation
- `Edit` - File modification
**Ineligible Tools:**
- `Bash` - Shell commands
- `WebFetch` - HTTP requests
- `WebSearch` - Web searches
- `NotebookEdit` - Jupyter notebooks
- `Task` - Sub-agent spawning
- `Skill` - Skill invocation
### 6.3 Permission Response Types
| Response | Message Sent | Behavior |
|----------|--------------|----------|
| `accept` | "continue" | Grants all requested tools |
| `deny` | "Permission denied. Find another approach without using that tool." | Rejects request |
| `steer` | User-provided text | Redirects with instructions |
### 6.4 Permission Message Recording
Permissions are recorded as assistant messages with metadata:
```typescript
{
role: 'assistant',
content: 'I need permission to...',
metadata: {
type: 'permission-request',
status: 'accept' | 'deny' | 'steer',
auto_accepted?: true,
denials: [...],
message_id: 'uuid'
}
}
```
---
## 7. Terminal Integration
### 7.1 Tmux Session Management
#### 7.1.1 Session Naming
- **Format:** `spiceflow-{adjective}-{noun}-{4digits}`
- **Examples:** `spiceflow-brave-fox-0042`, `spiceflow-calm-owl-1337`
- **Purpose:** Human-readable, unique identifiers
#### 7.1.2 Output Capture
```bash
# Create session
tmux new-session -d -s {session-name} -c {working-dir}
# Capture output to file
tmux pipe-pane -t {session-name} "cat >> /tmp/spiceflow-tmux-{name}.log"
# Capture visible pane
tmux capture-pane -t {session-name} -p -e -S -1000
```
### 7.2 Terminal Diff System
#### 7.2.1 Diff Types
| Type | When Used | Payload |
|------|-----------|---------|
| `full` | Initial load, >50% changed | `{lines: [...], hash, frame-id}` |
| `diff` | Partial update | `{changes: {line: content}, total-lines, hash, frame-id}` |
| `unchanged` | No changes | `{hash, frame-id}` |
#### 7.2.2 Frame Ordering
- **Frame ID:** Auto-incrementing counter (wraps at 2^53-1)
- **Purpose:** Prevent out-of-order updates from network delays
- **Client Logic:** Only apply frames with ID > last frame ID
#### 7.2.3 Change Detection
```clojure
(defn compute-line-diff [old-lines new-lines]
;; Compare line-by-line
;; Return map of {line-num -> new-content}
;; If >50% changed, return nil (signal full refresh))
```
### 7.3 Input Handling
#### 7.3.1 Key Mapping
| Input | tmux send-keys |
|-------|----------------|
| Regular text | `-l "{text}"` (literal) |
| Enter | `Enter` (key name) |
| Newline | `-l "\n"` |
| Ctrl+C | `\x03` (raw) |
| Ctrl+D | `\x04` (raw) |
| Tab | `\t` |
| Escape | `\x1b` |
| Arrow keys | `\x1b[A/B/C/D` |
#### 7.3.2 Input Batching
- **Client-side:** 30ms batching for rapid keystrokes
- **Server-side:** Immediate send to tmux
- **Feedback:** 100ms delayed terminal capture after input
### 7.4 Screen Size Presets
| Mode | Dimensions | Use Case |
|------|------------|----------|
| `portrait` | 40x24 | Phone portrait |
| `landscape` | 65x24 | Phone landscape |
| `desktop` | 100x24 | Split screen |
| `fullscreen` | 180x24 | Full terminal |
---
## 8. Push Notifications
### 8.1 VAPID Authentication (RFC 8292)
#### 8.1.1 Key Generation
```clojure
;; Generate EC P-256 key pair
(defn generate-keypair []
{:public-key ;; Uncompressed point (0x04 || x || y), base64url
:private-key ;; Raw scalar (32 bytes), base64url
})
```
#### 8.1.2 JWT Structure
```json
{
"aud": "https://push.service.com",
"exp": 1234567890,
"sub": "mailto:admin@example.com"
}
```
**Signature:** ES256 (ECDSA P-256 + SHA-256)
### 8.2 Web Push Encryption (RFC 8291)
#### 8.2.1 Encryption Flow
```
1. Decode client keys (p256dh, auth) from base64url
2. Generate ephemeral server EC key pair
3. Generate random 16-byte salt
4. ECDH with client public key → shared secret
5. HKDF-extract with auth secret
6. HKDF-expand for CEK (16 bytes) and nonce (12 bytes)
7. Pad plaintext (2-byte length prefix)
8. AES-128-GCM encrypt
```
#### 8.2.2 Message Format
```
salt (16) || record_size (4) || keyid_len (1) || keyid (65) || ciphertext
```
### 8.3 Notification Payload
```json
{
"title": "Permission Required",
"body": "Claude needs permission for: Write file.txt",
"sessionId": "uuid",
"sessionTitle": "My Session",
"tools": ["Write"]
}
```
### 8.4 Notification Timing
- **Delay:** 15 seconds after permission request
- **Condition:** Permission still pending (same message-id)
- **Auto-cleanup:** Subscriptions removed on 404/410 response
---
## 9. API Specification
### 9.1 Session Endpoints
#### GET /api/sessions
**Response:**
```json
[
{
"id": "uuid",
"provider": "claude",
"title": "My Session",
"status": "idle",
"working-dir": "/home/user/project",
"auto-accept-edits": false,
"created-at": "2024-01-01T00:00:00Z",
"updated-at": "2024-01-01T00:00:00Z"
}
]
```
#### POST /api/sessions
**Request:**
```json
{
"provider": "claude" | "opencode" | "tmux",
"working-dir": "/optional/path"
}
```
**Response:** Created session object
#### GET /api/sessions/:id
**Response:**
```json
{
"id": "uuid",
"provider": "claude",
"messages": [
{
"id": "uuid",
"role": "user",
"content": "Hello",
"created-at": "2024-01-01T00:00:00Z"
}
],
"pending-permission": null
}
```
#### PATCH /api/sessions/:id
**Request:**
```json
{
"title": "New Title",
"auto-accept-edits": true
}
```
**Response:** Updated session object
#### DELETE /api/sessions/:id
**Response:** 204 No Content
### 9.2 Message Endpoints
#### POST /api/sessions/:id/send
**Request:**
```json
{
"message": "User message text"
}
```
**Response:**
```json
{
"status": "sent"
}
```
#### POST /api/sessions/:id/permission
**Request:**
```json
{
"response": "accept" | "deny" | "steer",
"message": "Optional steer instructions"
}
```
**Response:**
```json
{
"status": "permission-response-sent"
}
```
### 9.3 Terminal Endpoints
#### GET /api/sessions/:id/terminal
**Query Parameters:**
- `fresh=true` - Invalidate cache
**Response:**
```json
{
"content": "terminal output...",
"alive": true,
"session-name": "spiceflow-brave-fox-0042",
"layout": "landscape",
"diff": {
"type": "diff",
"changes": {"5": "new line content"},
"total-lines": 24,
"hash": 12345,
"frame-id": 100
}
}
```
#### POST /api/sessions/:id/terminal/input
**Request:**
```json
{
"input": "ls -la\r"
}
```
**Response:**
```json
{
"status": "sent"
}
```
#### POST /api/sessions/:id/terminal/resize
**Request:**
```json
{
"mode": "landscape" | "portrait" | "desktop" | "fullscreen"
}
```
**Response:**
```json
{
"status": "resized",
"mode": "landscape"
}
```
### 9.4 Push Endpoints
#### GET /api/push/vapid-key
**Response:**
```json
{
"publicKey": "base64url-encoded-public-key"
}
```
#### POST /api/push/subscribe
**Request:**
```json
{
"endpoint": "https://push.service.com/...",
"keys": {
"p256dh": "base64url-client-key",
"auth": "base64url-auth-secret"
}
}
```
**Response:**
```json
{
"id": "subscription-uuid"
}
```
#### POST /api/push/unsubscribe
**Request:**
```json
{
"endpoint": "https://push.service.com/..."
}
```
**Response:** 204 No Content
### 9.5 Utility Endpoints
#### GET /api/health
**Response:**
```json
{
"status": "ok",
"service": "spiceflow"
}
```
#### GET /api/tmux/external
**Response:**
```json
[
{
"name": "my-session",
"working-dir": "/home/user"
}
]
```
#### POST /api/tmux/import
**Request:**
```json
{
"name": "my-session"
}
```
**Response:**
```json
{
"id": "spiceflow-my-session",
"name": "spiceflow-my-session",
"working-dir": "/home/user"
}
```
---
## 10. WebSocket Protocol
### 10.1 Connection
**Endpoint:** `/api/ws`
**Upgrade:** Standard WebSocket handshake
### 10.2 Client Messages
#### Subscribe to Session
```json
{
"type": "subscribe",
"session-id": "uuid"
}
```
#### Unsubscribe from Session
```json
{
"type": "unsubscribe",
"session-id": "uuid"
}
```
#### Heartbeat
```json
{
"type": "ping"
}
```
### 10.3 Server Messages
#### Connection Established
```json
{
"type": "connected"
}
```
#### Subscription Confirmed
```json
{
"type": "subscribed",
"session-id": "uuid"
}
```
#### Content Delta
```json
{
"event": "content-delta",
"session-id": "uuid",
"text": "streamed text chunk"
}
```
#### Permission Request
```json
{
"event": "permission-request",
"session-id": "uuid",
"permission-request": {
"tools": ["Write"],
"denials": [...]
},
"message-id": "uuid",
"message": {
"id": "uuid",
"content": "...",
"metadata": {...}
},
"auto-accepted": false
}
```
#### Message Stop
```json
{
"event": "message-stop",
"session-id": "uuid"
}
```
#### Terminal Update
```json
{
"event": "terminal-update",
"session-id": "uuid",
"content": "full terminal content",
"diff": {...}
}
```
#### Working Directory Update
```json
{
"event": "working-dir-update",
"session-id": "uuid",
"working-dir": "/new/path"
}
```
#### Error
```json
{
"event": "error",
"session-id": "uuid",
"message": "Error description"
}
```
#### Pong
```json
{
"type": "pong"
}
```
### 10.4 Reconnection Strategy
- **Max Attempts:** 5
- **Initial Delay:** 1 second
- **Backoff:** Exponential (1s, 2s, 4s, 8s, 16s)
- **Heartbeat:** Ping every 25 seconds
- **Pong Timeout:** 10 seconds
---
## 11. User Interface
### 11.1 Page Structure
#### Home Page (`/`)
```
┌─────────────────────────────────────────┐
│ ☰ Spiceflow 🔔 + ↻ │
├─────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ │
│ │ ● My Claude Session claude │ │
│ │ /home/user/project 2h ago │ │
│ └─────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ ○ Terminal Session tmux │ │
│ │ /home/user 1d ago │ │
│ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────┘
```
**Elements:**
- Header with branding and action buttons
- Session cards with status indicators
- Provider badges (color-coded)
- Relative timestamps
#### Session Page (`/session/:id`)
**Chat Mode (Claude/OpenCode):**
```
┌─────────────────────────────────────────┐
│ ← Session Title ⚙️ CLAUDE │
├─────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ │
│ │ User: Hello │ │
│ └─────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Assistant: Hi! How can I help? │ │
│ │ ● ● ● (thinking) │ │
│ └─────────────────────────────────┘ │
│ │
├─────────────────────────────────────────┤
│ [Type a message... ] [➤] │
└─────────────────────────────────────────┘
```
**Terminal Mode (tmux):**
```
┌─────────────────────────────────────────┐
│ ← brave-fox-0042 ⚙️ TERMINAL │
├─────────────────────────────────────────┤
│ $ pwd │
│ /home/user │
│ $ ls -la │
│ total 32 │
│ drwxr-xr-x 5 user user 4096 Jan 1 00:0│
│ -rw-r--r-- 1 user user 123 Jan 1 00:0│
│ $ │
├─────────────────────────────────────────┤
│ [^] [^C] [^D] | [y] [n] | [1-4] | [⇥] │
│ [-] 100% [+] | [📱] [📺] [🖥️] [⬜] [↓] │
└─────────────────────────────────────────┘
```
### 11.2 Permission UI
```
┌─────────────────────────────────────────┐
│ ⚠️ Claude needs permission │
├─────────────────────────────────────────┤
│ │
│ Write: /home/user/foo.md │
│ ┌─────────────────────────────────┐ │
│ │ + 1 # My Haiku │ │
│ │ + 2 │ │
│ │ + 3 Code flows like water │ │
│ │ + 4 Tests catch bugs silently │ │
│ │ + 5 Green lights bring peace │ │
│ └─────────────────────────────────┘ │
│ │
│ [Accept] [Deny] [No, and...] │
└─────────────────────────────────────────┘
```
### 11.3 Component Inventory
| Component | Purpose | Props |
|-----------|---------|-------|
| `SessionCard` | Session list item | `session` |
| `MessageList` | Chat history display | `messages, streamingContent, isThinking` |
| `InputBar` | Message input | `disabled, placeholder, autoFocus` |
| `PermissionRequest` | Permission approval UI | `permission, assistantName` |
| `FileDiff` | File change visualization | `tool, input, filePath` |
| `TerminalView` | Tmux display/input | `sessionId, autoScroll` |
| `SessionSettings` | Settings dropdown | `autoAcceptEdits, autoScroll, provider` |
| `PushToggle` | Notification toggle | (uses store) |
### 11.4 Responsive Behavior
| Breakpoint | Header | Layout |
|------------|--------|--------|
| Portrait | Full header | Vertical stack |
| Landscape mobile | Hamburger menu | Compact header |
| Desktop | Full header | All controls visible |
---
## 12. User Flows
### 12.1 Create and Chat with Claude
1. User opens app → sees session list
2. User taps **+** button → provider menu appears
3. User selects **Claude Code** → navigates to new session
4. User sees "No messages yet" state
5. User types message in input bar
6. User taps send → message appears immediately (optimistic)
7. Thinking indicator (●●●) appears
8. Streaming text appears incrementally
9. Response completes → thinking indicator disappears
10. User can continue conversation
### 12.2 Handle Permission Request
1. User sends message requesting file operation
2. Claude attempts Write/Edit/Bash
3. **Permission UI appears** with tool details
4. User reviews file diff (for Write/Edit)
5. User chooses:
- **Accept** → grants permission, streaming continues
- **Deny** → rejects, Claude finds alternative
- **No, and...** → enters steer mode
6. If steer: user types redirect instructions
7. Permission UI disappears
8. New response streams
### 12.3 Enable Auto-Accept
1. User opens session with Claude
2. User taps **⚙️** (settings gear)
3. User enables **Auto-accept edits** checkbox
4. Server saves preference (`auto-accept-edits: true`)
5. Future Write/Edit operations auto-approved
6. Permission messages appear with green ✓ status
7. No interruption for file operations
### 12.4 Use Terminal Session
1. User creates tmux session
2. User sees terminal with prompt
3. User taps terminal to focus
4. User types command (keyboard or quick buttons)
5. Input sent to tmux session
6. Output appears in terminal view
7. User can resize terminal (portrait/landscape/desktop/fullscreen)
8. User can use quick buttons (Ctrl+C, y/n, etc.)
### 12.5 Receive Push Notification
1. User enables push notifications (bell icon)
2. Browser prompts for permission
3. User grants permission
4. Subscription saved to server
5. Later: permission request pending for 15+ seconds
6. Server sends push notification
7. User's device shows notification
8. User taps notification → app opens to session
9. User sees permission UI, responds
---
## 13. Security Considerations
### 13.1 Permission Model
- **No auto-execute:** All tool operations require explicit approval (or opt-in auto-accept)
- **Auto-accept scope:** Limited to Write/Edit only, never Bash/network
- **Steer capability:** User can redirect AI without accepting dangerous operations
### 13.2 Push Security
- **VAPID authentication:** Server identity verified to push services
- **End-to-end encryption:** RFC 8291 AES-128-GCM encryption
- **Subscription cleanup:** Invalid subscriptions auto-removed
### 13.3 Data Protection
- **Local SQLite:** Data stored locally on server
- **No cloud sync:** Sessions not transmitted externally
- **Process isolation:** CLI processes run with server user permissions
### 13.4 API Security
- **CORS:** Configured for allowed origins
- **Input validation:** Session IDs, permission responses validated
- **No secrets in URLs:** Sensitive data in request bodies
---
## 14. Performance Requirements
### 14.1 Response Times
| Operation | Target | Maximum |
|-----------|--------|---------|
| Page load | <1s | 3s |
| Session list | <500ms | 2s |
| Message send | <100ms (optimistic) | 1s |
| WebSocket connect | <1s | 5s |
| Terminal input | <50ms | 200ms |
### 14.2 Streaming Performance
- **Content delta frequency:** Real-time as received
- **Terminal diff threshold:** >50% change triggers full refresh
- **Frame ordering:** Prevents out-of-order updates
### 14.3 Resource Usage
- **Active processes:** ConcurrentHashMap for thread-safe access
- **WebSocket connections:** Efficient broadcast to subscribers only
- **Terminal cache:** Per-session diff state
### 14.4 Scalability
- **Concurrent sessions:** Limited by server resources
- **WebSocket subscribers:** Multiple clients per session supported
- **Database:** SQLite (single-writer, multiple-reader)
---
## Appendix A: Configuration Reference
| Variable | Default | Description |
|----------|---------|-------------|
| `SPICEFLOW_PORT` | 3000 | Server port |
| `SPICEFLOW_HOST` | 0.0.0.0 | Server host |
| `SPICEFLOW_DB` | spiceflow.db | SQLite database path |
| `CLAUDE_SESSIONS_DIR` | ~/.claude/projects | Claude sessions directory |
| `OPENCODE_CMD` | opencode | OpenCode binary name |
## Appendix B: Database Schema
```sql
CREATE TABLE session_statuses (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
INSERT INTO session_statuses VALUES
(1, 'idle'), (2, 'processing'), (3, 'awaiting-permission');
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
provider TEXT NOT NULL,
external_id TEXT,
title TEXT,
working_dir TEXT,
spawn_dir TEXT,
status_id INTEGER DEFAULT 1 REFERENCES session_statuses(id),
pending_permission TEXT,
auto_accept_edits INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE messages (
id TEXT PRIMARY KEY,
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
role TEXT NOT NULL,
content TEXT NOT NULL,
metadata TEXT,
created_at TEXT NOT NULL
);
CREATE TABLE push_subscriptions (
id TEXT PRIMARY KEY,
endpoint TEXT NOT NULL UNIQUE,
p256dh TEXT NOT NULL,
auth TEXT NOT NULL,
user_agent TEXT,
created_at TEXT NOT NULL
);
CREATE TABLE vapid_keys (
id INTEGER PRIMARY KEY CHECK (id = 1),
public_key TEXT NOT NULL,
private_key TEXT NOT NULL,
created_at TEXT NOT NULL
);
```
## Appendix C: CLI Command Reference
### Claude Code
```bash
claude --output-format stream-json \
--input-format stream-json \
--verbose --print \
--resume <session-id> \
--allowedTools Write Edit \
-- <working-dir>
```
### OpenCode
```bash
script -qc "opencode run --format json --session <id> <message>" /dev/null
```
### tmux
```bash
# Create session
tmux new-session -d -s <name> -c <dir>
# Send keys
tmux send-keys -t <name> [-l] "<input>" [Enter]
# Capture pane
tmux capture-pane -t <name> -p -e -S -1000
# Resize window
tmux resize-window -t <name> -x <width> -y <height>
# Pipe output
tmux pipe-pane -t <name> "cat >> <file>"
```