- Reduce mobile terminal widths by 2 chars (portrait 42x24, landscape 86x24) - Add "Big mode" for mobile: desktop sizing (120x36) at 70% zoom - Click zoom percentage to reset to 100% - Add keyboard shortcuts submenu in session settings - Update PRD with all terminal features and documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1692 lines
54 KiB
Markdown
1692 lines
54 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.1.5 External Tmux Session Import
|
||
|
||
Users can import existing tmux sessions not managed by Spiceflow:
|
||
|
||
1. **List External:** `GET /api/tmux/external` returns sessions without `spiceflow-` prefix
|
||
2. **Import:** `POST /api/tmux/import` with session name
|
||
3. **Rename:** Server renames `{name}` to `spiceflow-{name}`
|
||
4. **Setup:** Enables pipe-pane capture for imported session
|
||
5. **Return:** Session available in Spiceflow with new prefixed ID
|
||
|
||
#### 4.1.6 Session Rename
|
||
|
||
Sessions can be renamed via `PATCH /api/sessions/:id`:
|
||
|
||
**Claude/OpenCode:**
|
||
- Updates title in database
|
||
- Session ID remains unchanged
|
||
|
||
**Tmux:**
|
||
- Renames tmux session via `tmux rename-session`
|
||
- **Session ID changes** to new `spiceflow-{name}` format
|
||
- Response includes `idChanged: true` and new session object
|
||
- Client navigates to new URL with `replaceState`
|
||
|
||
#### 4.1.7 Session Eject (Tmux only)
|
||
|
||
Removes session from Spiceflow management while keeping it running:
|
||
|
||
1. Rename tmux session from `spiceflow-{name}` to `{name}`
|
||
2. Delete session from Spiceflow database
|
||
3. Session continues running, attachable via `tmux attach -t {name}`
|
||
|
||
### 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
|
||
|
||
**Name Generation:**
|
||
- 30 adjectives × 30 nouns = 900 base combinations
|
||
- 4-digit random suffix (0000-9999)
|
||
- Docker-style naming convention
|
||
|
||
#### 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` | 42x24 | Phone portrait |
|
||
| `landscape` | 86x24 | Phone landscape |
|
||
| `desktop` | 120x36 | Split screen |
|
||
| `fullscreen` | 260x36 | Full terminal |
|
||
|
||
### 7.5 Auto Orientation Detection
|
||
|
||
On mobile devices, the terminal automatically resizes when the user rotates their phone:
|
||
|
||
```javascript
|
||
screen.orientation.addEventListener('change', () => {
|
||
const type = screen.orientation.type;
|
||
if (type.includes('portrait')) {
|
||
resizeScreen('portrait');
|
||
} else if (type.includes('landscape')) {
|
||
resizeScreen('landscape');
|
||
}
|
||
});
|
||
```
|
||
|
||
**Trigger Conditions:**
|
||
- Only on mobile (width < 640px or height < 450px)
|
||
- Uses `screen.orientation` API (modern browsers)
|
||
|
||
### 7.6 Font Zoom Control
|
||
|
||
Terminal text size can be adjusted from 50% to 150% in 5% increments:
|
||
|
||
| Control | Action |
|
||
|---------|--------|
|
||
| `-` button | Decrease font scale |
|
||
| `+` button | Increase font scale |
|
||
| Percentage display | Shows current zoom; click to reset to 100% |
|
||
|
||
**Visibility:** Zoom controls hidden on mobile portrait, visible on landscape/desktop.
|
||
|
||
### 7.7 Big Mode
|
||
|
||
Mobile users can enable "Big mode" from the settings menu to view more terminal content:
|
||
|
||
- **Resize:** Sets terminal to desktop dimensions (120x36)
|
||
- **Zoom:** Sets font scale to 70%
|
||
- **Access:** Settings menu (gear icon) → "Big mode" button
|
||
- **Visibility:** Only shown for tmux sessions on mobile devices
|
||
|
||
### 7.8 Session Eject
|
||
|
||
Tmux sessions can be "ejected" from Spiceflow management while keeping them running:
|
||
|
||
1. User clicks "Eject session" in settings menu
|
||
2. Server renames session from `spiceflow-{name}` to `{name}`
|
||
3. Session removed from Spiceflow database
|
||
4. User can reattach manually via `tmux attach -t {name}`
|
||
|
||
**Use Case:** Transfer session to local terminal for continued work.
|
||
|
||
### 7.9 Keyboard Shortcuts
|
||
|
||
| Shortcut | Action |
|
||
|----------|--------|
|
||
| `Ctrl+Down` | Scroll to bottom |
|
||
| `Ctrl+Shift+V` | Paste from clipboard |
|
||
| `Shift+Enter` | Send literal newline |
|
||
| `Shift+Tab` | Send reverse-tab escape sequence |
|
||
| `^` toggle | Enable Ctrl mode (next letter sends control character) |
|
||
|
||
### 7.10 Quick Action Buttons
|
||
|
||
| Button | Function | Color |
|
||
|--------|----------|-------|
|
||
| `^` | Toggle Ctrl mode | Gray (cyan ring when active) |
|
||
| `^C` | Send interrupt | Red |
|
||
| `^D` | Send EOF | Amber |
|
||
| `y` | Send 'y' | Green |
|
||
| `n` | Send 'n' | Red |
|
||
| `1-4` | Send number | Gray |
|
||
| `⇥` | Send Tab | Cyan |
|
||
| `⇤` | Send Shift+Tab | Cyan |
|
||
| `↵` | Send Enter | Green |
|
||
| `📋` | Paste clipboard | Violet |
|
||
|
||
**Visibility:** `^`, `y`, `n`, `1-4`, and paste hidden on mobile portrait.
|
||
|
||
### 7.11 ANSI Color Rendering
|
||
|
||
Terminal output preserves ANSI escape sequences:
|
||
- Converted to HTML via `ansi-to-html` library
|
||
- Default foreground: green (#22c55e)
|
||
- Background: transparent
|
||
- Supports standard terminal colors
|
||
|
||
---
|
||
|
||
## 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"
|
||
}
|
||
```
|
||
|
||
#### POST /api/sessions/:id/eject
|
||
|
||
Ejects a tmux session from Spiceflow management (tmux only).
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"status": "ejected",
|
||
"message": "Session ejected. Reattach with: tmux attach -t {name}",
|
||
"session-name": "my-session"
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
- 400: Not a tmux session
|
||
- 404: Session not found
|
||
|
||
---
|
||
|
||
## 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
|
||
|
||
### 10.5 Terminal Update Broadcasting
|
||
|
||
After tmux input, server broadcasts terminal updates:
|
||
|
||
1. Input received via `POST /api/sessions/:id/terminal/input`
|
||
2. Input sent to tmux immediately
|
||
3. 100ms delay for command execution
|
||
4. Fresh terminal content captured
|
||
5. Diff computed and broadcast via WebSocket
|
||
6. Broadcast always sent (even if unchanged) to ensure client sync
|
||
|
||
### 10.6 Full Frame Refresh
|
||
|
||
To handle potential drift, the server periodically sends full frames:
|
||
- Every 5 seconds during active streaming
|
||
- On explicit `fresh=true` request
|
||
- After terminal resize operations
|
||
|
||
---
|
||
|
||
## 11. User Interface
|
||
|
||
### 11.1 Page Structure
|
||
|
||
#### Home Page (`/`)
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ ☰ Spiceflow 🔔 + ↻ │
|
||
├─────────────────────────────────────────┤
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ ● 2 sessions processing │ │ (green badge, pulsing)
|
||
│ └─────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ ● My Claude Session claude │ │
|
||
│ │ /home/user/project 2h ago │ │
|
||
│ └─────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ ○ Terminal Session tmux │ │
|
||
│ │ /home/user 1d ago │ │
|
||
│ └─────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ + Import tmux session │ │ (if external sessions exist)
|
||
│ └─────────────────────────────────┘ │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
**Elements:**
|
||
- Header with branding and action buttons
|
||
- Processing sessions counter (green pulsing badge)
|
||
- Session cards with status indicators
|
||
- Provider badges (color-coded)
|
||
- Relative timestamps
|
||
- Import button for external tmux sessions
|
||
|
||
#### Session Page (`/session/:id`)
|
||
|
||
**Chat Mode (Claude/OpenCode):**
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ ← Session Title ⚙️ CLAUDE │
|
||
├─────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ User: Hello │ │
|
||
│ └─────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ Assistant: Hi! How can I help? │ │
|
||
│ │ ● ● ● (thinking) │ │
|
||
│ └─────────────────────────────────┘ │
|
||
│ │
|
||
├─────────────────────────────────────────┤
|
||
│ [⌨] [Type a message... ] [➤] │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
**Mobile Keyboard Toggle:**
|
||
- `⌨` button shows/hides mobile keyboard
|
||
- Addresses issue where keyboard can hide input field
|
||
- Toggles between up/down arrow indicators
|
||
|
||
**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 .md │ (file extension badge)
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ + 1 # My Haiku │ │
|
||
│ │ + 2 │ │
|
||
│ │ + 3 Code flows like water │ │
|
||
│ │ + 4 Tests catch bugs silently │ │
|
||
│ │ + 5 Green lights bring peace │ │
|
||
│ └─────────────────────────────────┘ │
|
||
│ │
|
||
│ [Accept] [Deny] [No, and...] │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
**File Diff Viewer:**
|
||
- **Write operations:** All lines shown as green additions (+)
|
||
- **Edit operations:** Old lines in red (-), new lines in green (+)
|
||
- **Line numbers:** Shown for both old and new content
|
||
- **File extension badge:** Displayed in top-right corner
|
||
- **Hover highlighting:** Lines highlight on mouse over
|
||
- **Tab handling:** Tabs rendered as 4 spaces
|
||
|
||
### 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 (<640px width, >450px height) | Full header | Vertical stack |
|
||
| Landscape mobile (<450px height) | Hamburger menu | Compact header |
|
||
| Desktop (≥640px width, ≥450px height) | Full header | All controls visible |
|
||
| XL Desktop (≥1280px width) | Full header | Mobile orientation buttons hidden |
|
||
|
||
### 11.5 Mobile-Specific UI Classes
|
||
|
||
| CSS Class | Behavior |
|
||
|-----------|----------|
|
||
| `portrait-hide` | Hidden on mobile portrait (width < 640px AND height > 450px) |
|
||
| `desktop-only` | Hidden on mobile (width < 640px OR height < 450px) |
|
||
| `mobile-only` | Hidden on XL desktop (width ≥ 1280px) |
|
||
| `landscape-mobile:hidden` | Hidden when height < 450px |
|
||
| `landscape-menu` | Shown only when height < 450px |
|
||
|
||
### 11.6 Message Condensing
|
||
|
||
Long messages (5+ lines) can be collapsed:
|
||
|
||
- **Threshold:** 5 lines minimum to show collapse toggle
|
||
- **Preview:** Shows first 3 lines when collapsed
|
||
- **Toggle:** Chevron indicator expands/collapses
|
||
- **Bulk action:** "Condense all" in settings menu
|
||
|
||
### 11.7 Thinking Indicator
|
||
|
||
Animated indicator when Claude is processing:
|
||
- Three bouncing dots (●●●)
|
||
- Separate from streaming content display
|
||
- Disappears when response completes or permission requested
|
||
|
||
### 11.8 Auto-Scroll Control
|
||
|
||
- **Default:** Enabled
|
||
- **Persistence:** Saved to localStorage (`spiceflow-auto-scroll`)
|
||
- **Toggle:** Available in session settings menu
|
||
- **Behavior:** Scrolls to bottom on new content
|
||
- **Override:** Ctrl+Down forces scroll regardless of setting
|
||
|
||
### 11.9 Session Status Indicators
|
||
|
||
| Status | Indicator |
|
||
|--------|-----------|
|
||
| Idle | Gray dot (static) |
|
||
| Processing | Green dot (pulsing) |
|
||
| Awaiting Permission | Amber dot (pulsing) |
|
||
| Tmux Alive | Green dot |
|
||
| Tmux Dead | Gray dot |
|
||
|
||
### 11.10 Markdown Rendering
|
||
|
||
Assistant messages render Markdown with:
|
||
- Headings (h1-h6) with appropriate sizing
|
||
- Code blocks with monospace font and background
|
||
- Inline code with background highlight
|
||
- Lists (ordered and unordered)
|
||
- Blockquotes with left border
|
||
- Links styled in orange (spice color)
|
||
- Line breaks preserved (`breaks: true`)
|
||
|
||
---
|
||
|
||
## 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
|
||
|
||
### 12.6 Import External Tmux Session
|
||
|
||
1. User has existing tmux session running (e.g., `dev-session`)
|
||
2. User opens Spiceflow home page
|
||
3. "Import tmux session" button appears
|
||
4. User clicks import → dropdown shows available sessions
|
||
5. User selects `dev-session`
|
||
6. Server renames to `spiceflow-dev-session`
|
||
7. Session appears in Spiceflow list
|
||
8. User can now manage session via Spiceflow
|
||
|
||
### 12.7 Eject Tmux Session
|
||
|
||
1. User opens tmux session in Spiceflow
|
||
2. User clicks settings gear (⚙️)
|
||
3. User clicks "Eject session"
|
||
4. Confirmation alert shows reattach command
|
||
5. Session renamed from `spiceflow-{name}` to `{name}`
|
||
6. User redirected to home page
|
||
7. Session removed from Spiceflow but continues running
|
||
8. User can reattach via `tmux attach -t {name}`
|
||
|
||
### 12.8 Rotate Phone (Terminal)
|
||
|
||
1. User viewing terminal session on phone
|
||
2. User rotates phone from portrait to landscape
|
||
3. `screen.orientation` change event fires
|
||
4. App detects orientation is now landscape
|
||
5. Terminal automatically resizes to landscape dimensions (88x24)
|
||
6. Fresh terminal content fetched after 150ms
|
||
7. UI updates to show landscape-specific controls
|
||
|
||
---
|
||
|
||
## 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>"
|
||
```
|