# VibeTunnel Architecture Analysis - Mario's Technical Deep Dive

This document contains comprehensive technical insights from Mario's debugging session about VibeTunnel's architecture, critical performance issues, and detailed solutions.

## Executive Summary

Mario identified two critical issues causing performance problems in VibeTunnel:

1. **850MB Session Bug**: External terminal sessions (via `fwd.ts`) bypass the clear sequence truncation in `stream-watcher.ts`, sending entire gigabyte files instead of the last 2MB
2. **Resize Loop**: Claude terminal app issues full clear sequence (`\x1b[2J`) and re-renders entire scroll buffer on every resize event, creating exponential data growth

Note: A third issue with Node-PTY's shared pipe architecture causing Electron crashes has already been resolved with a custom PTY implementation.

## System Architecture

### Core Components

```
┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Client    │────▶│  Web Server  │────▶│ PTY Process │
└─────────────┘     └──────────────┘     └─────────────┘
      │                    │                     │
      │                    ▼                     ▼
      │              ┌──────────┐         ┌──────────┐
      └─────────────▶│ Terminal │         │ Ascinema │
                     │ Manager  │         │  Files   │
                     └──────────┘         └──────────┘
```

### Detailed Sequence Flow

```mermaid
sequenceDiagram
    participant UI as Browser/Electron
    participant WS as WebSocket
    participant Server as Node Server
    participant PTY as PTYManager
    participant FWD as fwd.ts
    participant Proc as User Process
    
    UI->>WS: keystrokes
    WS->>Server: /api/sessions/:id/input
    Server->>PTY: IPC Socket message
    PTY->>Proc: write to stdin
    Proc-->>PTY: stdout (ANSI sequences)
    PTY-->>Server: write to ascinema file
    alt External Terminal
        FWD-->>TTY: mirror stdout to terminal
    end
    Server-->>UI: SSE stream (truncated)
```

### Key Files and Their Roles

| File | Purpose | Critical Functions |
|------|---------|-------------------|
| `server.ts` | Main web server | HTTP endpoints, WebSocket handling |
| `pty-manager.ts` | PTY lifecycle management | `createSession()`, `setupPtyHandlers()` |
| `stream-watcher.ts` | Monitors ascinema files | `sendExistingContent()` - implements clear truncation |
| `fwd.ts` | External terminal forwarding | Process spawning, **BYPASSES TRUNCATION** |
| `terminal-manager.ts` | Binary buffer rendering | Converts ANSI to binary cells format |

### Data Flow Paths

#### Input Path (Keystroke → Terminal)
1. Browser captures key press
2. WebSocket sends to `/api/sessions/:id/input`
3. Server writes to IPC socket
4. PTY Manager writes to process stdin
5. Process executes command

#### Output Path (Terminal → Browser)
1. Process writes to stdout
2. PTY Manager captures via `onData` handler
3. Writes to ascinema file (with write queue for backpressure)
4. Stream watcher monitors file changes
5. For existing content: **scans for last clear sequence**
6. Client receives via:
   - SSE: `/api/sessions/:id/stream` (text/ascinema format)
   - WebSocket: `/buffers` (binary cell format)

### Binary Cell Buffer Format

The terminal manager pre-renders terminal output into a binary format for efficient transmission:

```
For each cell at (row, column):
- Character (UTF-8 encoded)
- Foreground color (RGB values)
- Background color (RGB values)
- Attributes (bold, italic, underline, etc.)
```

Benefits:
- Server-side ANSI parsing eliminates client CPU usage
- Efficient binary transmission reduces bandwidth
- Only last 10,000 lines kept in memory
- Client simply renders pre-computed cells

## Critical Bugs Analysis

### 1. The 850MB Session Loading Bug

**Symptom**: Sessions with large output (850MB+) cause infinite loading and browser unresponsiveness.

**Root Cause**: External terminal sessions via `fwd.ts` bypass the clear sequence truncation logic.

**Technical Details**:
```javascript
// In stream-watcher.ts - WORKING CORRECTLY
sendExistingContent() {
  // Scans backwards for last clear sequence
  const lastClear = content.lastIndexOf('\x1b[2J');
  // Sends only content after clear
  return content.slice(lastClear); // 2MB instead of 850MB
}
```

**Evidence from Testing**:
- Test file: 980MB containing 2,400 clear sequences
- Server-created sessions: Correctly send only last 2MB
- External terminal sessions: Send entire 980MB file
- Processing time: 2-3 seconds to scan 1GB file
- Client receives instant replay for 2MB truncated content

**The Issue**: External terminal path doesn't trigger `sendExistingContent()`, sending gigabyte files to clients.

### 2. Resize Event Performance Catastrophe

**Problem**: Each resize event causes Claude to re-render the entire terminal history.

**Claude's Behavior**:
```
1. Resize event received
2. Claude issues clear sequence: \x1b[2J
3. Re-renders ENTIRE scroll buffer from line 1
4. Rendering causes viewport changes
5. Viewport changes trigger resize event
6. GOTO step 1 (infinite loop)
```

**Technical Evidence**:
- In 850MB session: each resize → full buffer re-render
- Claude renders from "Welcome to Claude" message every time
- Mobile UI particularly problematic (frequent resize events)
- Header button position shifts during rendering indicate viewport instability
- Session with 39 resize events can generate 850MB+ files

**Contributing Factors**:
- React Ink (TUI framework) unnecessarily re-renders entire components
- Session-detail-view has buggy resize observer
- Mobile Safari behaves differently than desktop at same viewport size
- Touch events vs mouse events complicate scrolling behavior

### 3. Node-PTY Architecture Flaw (ALREADY FIXED)

This issue has been resolved by implementing a custom PTY solution without the shared pipe architecture.

## Ascinema Format Details

VibeTunnel uses the ascinema format for recording terminal sessions:

```javascript
// Format: [timestamp, event_type, data]
[1.234, "o", "Hello World\n"]     // Output event
[1.235, "i", "k"]                 // Input event (keypress)
[1.236, "r", "80x24"]             // Resize event
```

Clear sequence detection:
```javascript
const CLEAR_SEQUENCE = '\x1b[2J';  // ANSI clear screen
const CLEAR_WITH_HOME = '\x1b[H\x1b[2J'; // Home + clear

function findLastClearSequence(buffer) {
    // Search from end for efficiency
    let lastClear = buffer.lastIndexOf(CLEAR_SEQUENCE);
    return lastClear === -1 ? 0 : lastClear;
}
```

## Proposed Solutions

### Priority 1: Fix External Terminal Clear Truncation (IMMEDIATE)

**Problem**: External terminal sessions don't use `sendExistingContent()` truncation.

**Investigation Needed**:
1. Trace how `fwd.ts` connects to client streams
2. Determine why it bypasses stream-watcher's truncation
3. Ensure external terminals use same code path as server sessions
4. Test with 980MB file to verify fix

**Expected Impact**: Immediate fix for users experiencing infinite loading with large sessions.

### Priority 2: Fix Resize Handling (INVESTIGATION)

**Debugging Approach**:
1. Instrument session-detail-view with resize observer logging
2. Identify what causes viewport expansion
3. Implement resize event debouncing
4. Fix mobile-specific issues:
   - Keyboard state affects scrolling
   - Touch vs mouse event handling
   - Scrollbar visibility problems

**Code to Add**:
```javascript
// Add to session-detail-view
let resizeCount = 0;
new ResizeObserver((entries) => {
    console.log(`Resize ${++resizeCount}:`, entries[0].contentRect);
    // Debounce resize events
    clearTimeout(this.resizeTimeout);
    this.resizeTimeout = setTimeout(() => {
        this.handleResize();
    }, 100);
}).observe(this.terminalElement);
```

## Implementation Details

### Write Queue Implementation

The PTY manager implements backpressure handling:

```javascript
class WriteQueue {
    constructor(writer) {
        this.queue = [];
        this.writing = false;
        this.writer = writer;
    }
    
    async write(data) {
        this.queue.push(data);
        if (!this.writing) {
            await this.flush();
        }
    }
    
    async flush() {
        this.writing = true;
        while (this.queue.length > 0) {
            const chunk = this.queue.shift();
            await this.writer.write(chunk);
        }
        this.writing = false;
    }
}
```

### Platform-Specific Considerations

**macOS**:
- Screen Recording permission required for terminal access
- Terminal.app specific behaviors and quirks

**Mobile Safari**:
- Different behavior than desktop Safari at same viewport
- Touch events complicate scrolling
- Keyboard state affects scroll behavior
- Missing/hidden scrollbars
- Viewport meta tag issues

**Windows (Future)**:
- ConPTY vs WinPTY support
- Different ANSI sequence handling
- Path normalization requirements

## Performance Metrics

| Metric | Current | After Fix |
|--------|---------|-----------|
| 980MB session initial load | Infinite/Crash | 2-3 seconds |
| Data sent to client | 980MB | 2MB |
| Memory per terminal | 50-100MB | Target: 10MB |
| Clear sequence scan time | N/A | ~2 seconds for 1GB |
| Resize event storms | Exponential growth | Debounced |

## Testing and Debugging

### Test Large Session Handling
```bash
# Create large session file
cd web
npm run dev

# In another terminal, create session
SESSION_ID=$(curl -X POST localhost:3000/api/sessions | jq -r .id)

# Stop server, inject large file
cp /path/to/850mb-test-file ~/.vibetunnel/sessions/$SESSION_ID/stdout

# Restart and verify truncation works
npm run dev
```

### Debug Resize Events
```javascript
// Add to any component to detect resize loops
window.addEventListener('resize', () => {
    console.count('resize');
    console.trace('Resize triggered from:');
});
```

### Monitor Network Traffic
- Check `/api/sessions/:id/stream` response size
- Verify only sends data after last clear
- Monitor WebSocket `/buffers` for binary updates

## Architectural Insights

### Why Current Architecture Works (When Not Bugged)

1. **Simplicity**: "Es ist die todeleinfachste Variante" - It's the simplest possible approach
2. **Efficiency**: 2MB instead of 980MB transmission after clear sequence truncation
3. **Server-side rendering**: Binary cell format eliminates client ANSI parsing

### Community Contribution Challenges

- High development velocity makes contribution difficult
- "Velocity kills" - rapid changes discourage contributors
- LitElement/Web Components unfamiliar to most developers
- Large file sizes cause AI tools to refuse processing

### Future Architecture Considerations

**Go Migration Benefits**:
- Automatic test dependency tracking
- Only runs tests that changed
- Pre-allocated buffers minimize GC
- Better suited for AI-assisted development

**Rust Benefits**:
- 2MB static binary
- 10MB RAM usage
- Direct C interop for PTY code
- No garbage collection overhead

## Action Plan Summary

1. **Immediate (End of Week)**: Fix external terminal truncation bug
   - Debug why `fwd.ts` bypasses `sendExistingContent()`
   - Deploy fix for immediate user relief

2. **Short Term**: Comprehensive resize fix
   - Debug session-detail-view triggers
   - Implement proper debouncing
   - Fix mobile-specific issues

3. **Long Term**: Consider architecture migration
   - Evaluate Rust forward binary
   - Consider Go for web server
   - Maintain backwards compatibility

## Key Technical Quotes

- "Wir schicken 2MB statt 980MB" - We send 2MB instead of 980MB
- "Die haben einen Shared Pipe, wo alle reinschreiben" - They have a shared pipe where everyone writes
- "Es gibt keinen Grund, warum ich von da weg alles neu rendern muss" - There's no reason to re-render everything from the beginning
- "Das ist known good" - Referring to battle-tested implementations

This architecture analysis provides the technical foundation for fixing VibeTunnel's critical performance issues while maintaining its elegant simplicity.