# Technical Documentation: amantus-ai/vibetunnel

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/amantus-ai/vibetunnel)

### Overview (`README.md`)

# VibeTunnel 🚇 - Turn any browser into your terminal; command agents from the road (vt.sh)


<p align="center">
  <img src="assets/banner.png" alt="VibeTunnel Banner" />
</p>

<p align="center">
  <strong>Turn any browser into your Mac terminal.</strong><br>
  VibeTunnel proxies your terminals right into the browser, so you can vibe-code anywhere.
</p>

<p align="center">
  <a href="https://github.com/amantus-ai/vibetunnel/releases/latest"><img src="https://img.shields.io/badge/Download-macOS-blue" alt="Download"></a>
  <a href="https://www.npmjs.com/package/vibetunnel"><img src="https://img.shields.io/npm/v/vibetunnel.svg" alt="npm version"></a>
  <a href="https://formulae.brew.sh/cask/vibetunnel"><img src="https://img.shields.io/homebrew/cask/v/vibetunnel" alt="Homebrew"></a>
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node.js-22.12--24.x-339933?logo=node.js" alt="Node.js 22.12 through 24.x"></a>
  <a href="https://discord.gg/3Ub3EUwrcR"><img src="https://img.shields.io/discord/1394471066990280875?label=Discord&logo=discord" alt="Discord"></a>
  <a href="https://twitter.com/vibetunnel"><img src="https://img.shields.io/twitter/follow/vibetunnel?style=social" alt="Twitter"></a>
</p>

<p align="center">
  <a href="https://www.npmjs.com/package/vibetunnel"><img src="https://img.shields.io/badge/Linux-Supported-brightgreen" alt="Linux Support"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-green" alt="License"></a>
  <a href="https://www.apple.com/macos/"><img src="https://img.shields.io/badge/macOS-14.0+-red" alt="macOS 14.0+"></a>
  <a href="https://support.apple.com/en-us/HT211814"><img src="https://img.shields.io/badge/Apple%20Silicon-Required-orange" alt="Apple Silicon"></a>
  <a href="https://vibetunnel.sh/#support"><img src="https://img.shields.io/badge/Support%20us-on%20Polar-purple" alt="Support us on Polar"></a>
  <a href="https://deepwiki.com/amantus-ai/vibetunnel"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
</p>

<p align="center">
  <a href="https://docs.vibetunnel.sh">Documentation</a> •
  <a href="https://github.com/amantus-ai/vibetunnel/releases">Releases</a> •
  <a href="https://discord.gg/3Ub3EUwrcR">Discord</a> •
  <a href="https://twitter.com/vibetunnel">Twitter</a>
</p>

## Table of Contents

- [Why VibeTunnel?](#why-vibetunnel)
- [Installation Options](#installation-options)
- [Quick Start](#quick-start)
- [Features](#features)
- [Architecture](#architecture)
- [Remote Access Options](#remote-access-options)
- [Git Follow Mode](#git-follow-mode)
- [Terminal Title Management](#terminal-title-management)
- [Authentication](#authentication)
- [npm Package](#npm-package)
- [Building from Source](#building-from-source)
- [Development](#development)
- [Poltergeist Integration](#poltergeist-integration)
- [Documentation](#documentation)
- [macOS Permissions](#macos-permissions)
- [Contributing](#contributing)
- [Support VibeTunnel](#support-vibetunnel)
- [Credits](#credits)
- [License](#license)

## Why VibeTunnel?

Ever wanted to check on your AI agents while you're away? Need to monitor that long-running build from your phone? Want to share a terminal session with a colleague without complex SSH setups? VibeTunnel makes it happen with zero friction.

## Installation Options

### macOS App (Recommended for Mac users)
The native macOS app provides the best experience with menu bar integration and automatic updates.

### npm Package (Linux & Headless Systems)
For Linux servers, Docker containers, or headless macOS systems, install via npm:

```bash
npm install vibetunnel
```

This gives you the full VibeTunnel server with web UI, just without the macOS menu bar app. See the [npm Package section](#npm-package) for detailed usage.

## Quick Start

### Requirements

**macOS App**: Requires an Apple Silicon Mac (M1+). Intel Macs are not supported for the native app.

**npm Package**: Works on Node.js 22.12 through 24.x, including Intel Macs and Linux. Windows is not yet supported ([#252](https://github.com/amantus-ai/vibetunnel/issues/252)).

### 1. Download & Install

#### Option 1: Direct Download
[Download VibeTunnel](https://github.com/amantus-ai/vibetunnel/releases/latest) and drag it to your Applications folder.

#### Option 2: Homebrew
```bash
brew install --cask vibetunnel
```

### 2. Launch VibeTunnel

VibeTunnel lives in your menu bar. Click the icon to start the server.

### 3. Use the `vt` Command

The `vt` command is a smart wrapper that forwards your terminal sessions through VibeTunnel:

**How it works**:
- `vt` is a bash script that internally calls `vibetunnel fwd` to forward terminal output
- It provides additional features like shell alias resolution and session title management
- Available from both the Mac app and npm package installations

**Installation sources**:
- **macOS App**: Installs the `vt` wrapper script at `/usr/local/bin/vt` during installation
- **npm Package**: Installs `vt` globally, with intelligent Mac app detection

**Smart detection**:
When you run `vt` from the npm package, it:
1. Checks if the Mac app is installed at `/Applications/VibeTunnel.app`
2. If found, forwards to the Mac app's `vt` for the best experience
3. If not found, uses the npm-installed `vibetunnel fwd`
4. This ensures `vt` always uses the best available implementation

```bash
# Run any command in the browser
vt pnpm run dev
vt npm test
vt python script.py

# Use your shell aliases
vt gs              # Your 'git status' alias works!
vt claude-danger   # Custom aliases are resolved

# Open an interactive shell
vt --shell         # or vt -i

# Git follow mode
vt follow          # Follow current branch
vt follow main     # Switch to main and follow
vt unfollow       # Stop following

# For more examples and options, see "The vt Forwarding Command" section below
```

### Git Repository Scanning on First Session

When opening a new session for the first time, VibeTunnel's working directory scanner will look for Git repositories. By default, this scans your home directory, which may trigger macOS permission prompts for accessing protected folders (like Desktop, Documents, Downloads, iCloud Drive, or external volumes).

To avoid these prompts:
- **Option 1**: Navigate to your actual projects directory before opening a session
- **Option 2**: Accept the one-time permission prompts (they won't appear again)

This only happens on the first session when the scanner discovers your Git repositories. For more details about macOS privacy-protected folders, see [this explanation](https://eclecticlight.co/2025/02/24/gaining-access-to-privacy-protected-folders/).

### 4. Open Your Dashboard

Visit [http://localhost:4020](http://localhost:4020) to see all your terminal sessions.

## Features

- **🌐 Browser-Based Access** - Control your Mac terminal from any device with a web browser
- **🚀 Zero Configuration** - No SSH keys, no port forwarding, no complexity
- **🤖 AI Agent Friendly** - Perfect for monitoring Claude Code, ChatGPT, or any terminal-based AI tools
- **📊 Session Activity Indicators** - Real-time activity tracking shows which sessions are active or idle
- **🔄 Git Follow Mode** - Terminal automatically follows your IDE's branch switching
- **⌨️ Smart Keyboard Handling** - Intelligent shortcut routing with toggleable capture modes. When capture is active, use Cmd+1...9/0 (Mac) or Ctrl+1...9/0 (Linux) to quickly switch between sessions
- **🔒 Secure by Design** - Multiple authentication modes, localhost-only mode, or secure tunneling via Tailscale/ngrok
- **📱 Mobile Ready** - Native iOS app and responsive web interface for phones and tablets
- **🎬 Session Recording** - All sessions recorded in asciinema format for later playback
- **⚡ High Performance** - Optimized Node.js server with minimal resource usage
- **🍎 Apple Silicon Native** - Optimized for Apple Silicon (M1+) Macs with ARM64-only binaries
- **🐚 Shell Alias Support** - Your custom aliases and shell functions work automatically

> **Note**: The iOS app is still work in progress and not recommended for production use yet.

## Architecture

VibeTunnel consists of three main components:

1. **macOS Menu Bar App** - Native Swift application that manages the server lifecycle
2. **Node.js Server** - High-performance TypeScript server handling terminal sessions
3. **Web Frontend** - Modern web interface using Lit components and ghostty-web

The server runs as a standalone Node.js executable with embedded modules, providing excellent performance and minimal resource usage.

## Remote Access Options

### Option 1: Tailscale (Recommended)

[Tailscale](https://tailscale.com) creates a secure peer-to-peer VPN network between your devices. It's the most secure option as traffic stays within your private network without exposing VibeTunnel to the public internet.

**How it works**: Tailscale creates an encrypted WireGuard tunnel between your devices, allowing them to communicate as if they were on the same local network, regardless of their physical location.

#### Basic Setup
1. Install Tailscale on your Mac: [Download from Mac App Store](https://apps.apple.com/us/app/tailscale/id1475387142), [Direct Download](https://tailscale.com/download/macos), or a CLI/daemon package such as nix-darwin
2. Install Tailscale on your remote device:
   - **iOS**: [Download from App Store](https://apps.apple.com/us/app/tailscale/id1470499037)
   - **Android**: [Download from Google Play](https://play.google.com/store/apps/details?id=com.tailscale.ipn)
   - **Other platforms**: [All Downloads](https://tailscale.com/download)
3. Sign in to both devices with the same account
4. If using VibeTunnel's Tailscale Serve integration, ensure Tailscale Serve is enabled in your [tailnet settings](https://login.tailscale.com/f/serve)
5. Find your Mac's Tailscale hostname in the menu bar app or with `tailscale status` (e.g., `my-mac.tailnet-name.ts.net`)
6. Access VibeTunnel at `http://[your-tailscale-hostname]:4020`

VibeTunnel detects standard CLI locations and nix-darwin system/user profiles, and falls back to `tailscale status --json` when the macOS app API is unavailable.

#### Enhanced Tailscale Features

VibeTunnel now supports advanced Tailscale integration with **Private** and **Public** access modes:

##### Private Mode (Default)
- **What it does**: Provides secure HTTPS access within your Tailscale network only
- **Access URL**: `https://[your-machine-name].[tailnet-name].ts.net`
- **Security**: Traffic stays within your private tailnet
- **Best for**: Personal use, accessing your terminals from your own devices

##### Public Mode (Tailscale Funnel)
- **What it does**: Exposes VibeTunnel to the public internet via Tailscale Funnel
- **Access URL**: Same as Private mode but accessible from anywhere
- **Security**: Still uses HTTPS encryption, but accessible without Tailscale login
- **Best for**: Sharing terminal sessions with colleagues, temporary public access
- **Requirements**: Funnel must be enabled on your tailnet (see configuration below)

#### Configuring Tailscale Funnel

To use Public mode, you need to enable Funnel on your tailnet:

1. **Enable Funnel for your tailnet** by adding this ACL policy in the [Tailscale Admin Console](https://login.tailscale.com/admin/acls):
   ```json
   "nodeAttrs": [
       {
           "target": ["autogroup:member"], // All members of your tailnet
           "attr":   ["funnel"], 
       },
   ],
   ```

2. **Switch between modes** in VibeTunnel:
   - Open VibeTunnel Settings → Remote Access
   - Toggle between "Private (Tailnet Only)" and "Public (Internet)"
   - The UI will show the transition status and confirm when the mode is active

#### HTTPS Support

Both Private and Public modes automatically provide **HTTPS access**:
- Tailscale Serve creates an HTTPS proxy to VibeTunnel's local server
- SSL certificates are managed automatically by Tailscale
- No manual certificate configuration needed
- WebSocket connections work seamlessly over HTTPS/WSS

**Benefits**:
- **End-to-end encrypted** traffic
- **Automatic HTTPS** with valid certificates  
- **Works behind NAT** and firewalls
- **Zero configuration** after initial setup
- **Flexible access control** - choose between private tailnet or public internet access
- **No port forwarding** required

#### Troubleshooting

**"Tailscale Serve unavailable - using fallback mode"**: This is normal if you don't have Tailscale admin permissions. VibeTunnel will work perfectly using direct HTTP access at `http://[your-tailscale-hostname]:4020`.

**"Applying mode configuration..."**: When switching between Private and Public modes, it may take a few seconds for Tailscale to reconfigure. This is normal.

**"Funnel requires admin permissions"**: You need to be a tailnet admin to enable Funnel. Contact your tailnet admin or create your own tailnet if needed.

**WebSocket connections fail**: Make sure you're using the HTTPS URL when accessing VibeTunnel through Tailscale Serve. The WebSocket authentication tokens are automatically handled.

### Option 2: ngrok

[ngrok](https://ngrok.com) creates secure tunnels to your localhost, making VibeTunnel accessible via a public URL. Perfect for quick sharing or temporary access.

**How it works**: ngrok establishes a secure tunnel from a public endpoint to your local VibeTunnel server, handling SSL/TLS encryption and providing a unique URL for access.

**Setup Guide**:
1. Create a free ngrok account: [Sign up for ngrok](https://dashboard.ngrok.com/signup)
2. Copy your auth token from the [ngrok dashboard](https://dashboard.ngrok.com/get-started/your-authtoken)
3. Add the token in VibeTunnel settings (Settings → Remote Access → ngrok)
4. Enable ngrok tunneling in VibeTunnel
5. Share the generated `https://[random].ngrok-free.app` URL

**Benefits**:
- Public HTTPS URL with SSL certificate
- No firewall configuration needed
- Built-in request inspection and replay
- Custom domains available (paid plans)

**Note**: Free ngrok URLs change each time you restart the tunnel. You can claim one free static domain per user, or upgrade to a paid plan for multiple domains.

### Option 3: Local Network
1. Configure authentication (see Authentication section)
2. Switch to "Network" mode
3. Access via `http://[your-mac-ip]:4020`

### Option 4: Cloudflare Quick Tunnel
1. Install [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/)
2. Run `cloudflared tunnel --url http://localhost:4020`
3. Access via the generated `*.trycloudflare.com` URL

### Option 5: Pinggy
1. Install the [Pinggy CLI](https://pinggy.io/docs/cli/): `npm install pinggy`
2. Open VibeTunnel Settings → Remote → Pinggy Integration
3. Optionally save a Pinggy access token for a persistent subdomain or custom domain
4. Enable the tunnel and open the displayed public HTTPS URL

Free tunnels use a new URL after reconnecting. A persistent subdomain or custom domain requires a Pinggy plan that supports it and must be assigned to the saved token in the [Pinggy dashboard](https://dashboard.pinggy.io/domains).

### Option 6: Pangolin
1. Create a Site in [Pangolin](https://app.pangolin.net) and copy its Newt endpoint, ID, and secret
2. Install [Newt](https://docs.pangolin.net/manage/sites/install-site): `curl -fsSL https://static.pangolin.net/get-newt.sh | bash`
3. Open VibeTunnel Settings → Remote → Pangolin Integration and save the site credentials
4. In Pangolin, create a public Resource targeting `http://localhost:4020`
5. Connect the site in VibeTunnel and use the Resource URL configured by Pangolin

VibeTunnel stores the Newt site credentials in Keychain and starts Newt with a protected temporary config file, keeping the secret out of process arguments and environment variables.

## Git Follow Mode

Git Follow Mode keeps your main repository checkout synchronized with the branch you're working on in a Git worktree. This allows agents to work in worktrees while your IDE, server, and other tools stay open on the main repository - they'll automatically update when the worktree switches branches.

### What is Follow Mode?

Follow mode creates a seamless workflow for agent-assisted development:
- Agents work in worktrees → Main repository automatically follows their branch switches
- Keep Xcode/IDE open → It updates automatically without reopening projects
- Server stays running → No need to restart servers in different folders
- Zero manual intervention → Main repo stays in sync with active development

### Quick Start

```bash
# From a worktree - enable follow mode for this worktree
vt follow

# From main repo - follow current branch's worktree (if it exists)
vt follow

# From main repo - follow a specific branch's worktree
vt follow feature/new-api

# From main repo - follow a worktree by path
vt follow ~/project-feature

# Disable follow mode
vt unfollow
```

### How It Works

1. **Git Hooks**: VibeTunnel installs lightweight Git hooks (post-commit, post-checkout) in worktrees that detect branch changes
2. **Main Repo Sync**: When you switch branches in a worktree, the main repository automatically checks out to the same branch
3. **Smart Handling**: If the main repo has uncommitted changes, follow mode pauses to prevent data loss
4. **Development Continuity**: Your IDE, servers, and tools running on the main repo seamlessly follow your active work
5. **Clean Uninstall**: When you run `vt unfollow`, Git hooks are automatically removed and any original hooks are restored

### Common Workflows

#### Agent Development with Worktrees
```bash
# Create a worktree for agent development
git worktree add ../project-agent feature/new-feature

# Enable follow mode on the main repo
cd ../project && vt follow

# Agent works in the worktree while you stay in main repo
# When agent switches branches in worktree, your main repo follows!
# Your Xcode/IDE and servers stay running without interruption
```


### Technical Details

Follow mode stores the worktree path in your main repository's Git config:
```bash
# Check which worktree is being followed
git config vibetunnel.followWorktree

# Follow mode is active when this returns a path
# The config is managed by vt commands - manual editing not recommended
```

For more advanced Git worktree workflows, see our [detailed worktree documentation](docs/worktree.md).

## Terminal Title Management

VibeTunnel provides terminal title management to help you track sessions:

### Title Modes

- **Static Mode**: Shows working directory and command
  - Example: `~/projects/app — npm run dev`
- **Filter Mode**: Blocks all title changes from applications
  - Useful when you have your own terminal management system
- **None Mode**: No title management - applications control their own titles

### Activity Detection

Activity indicators are based on recent input/output and drive active/idle UI states.

## Authentication

VibeTunnel provides multiple authentication modes to secure your terminal sessions:

### Authentication Modes

#### 1. System Authentication (Default)
Uses your operating system's native authentication:
- **macOS**: Authenticates against local user accounts
- **Linux**: Uses PAM (Pluggable Authentication Modules)
- Login with your system username and password

#### 2. Environment Variable Authentication
Simple authentication for deployments:
```bash
export VIBETUNNEL_USERNAME=admin
export VIBETUNNEL_PASSWORD=your-secure-password
npm run start
```

JWT signing keys are generated once in `~/.vibetunnel/jwt-secret` with owner-only
permissions, so browser sessions remain valid across server restarts. Set `JWT_SECRET`
to use an operator-managed signing secret instead; rotating either value logs out clients.

#### 3. SSH Key Authentication
Use Ed25519 SSH keys from `~/.ssh/authorized_keys`:
```bash
# Enable SSH key authentication
npm run start -- --enable-ssh-keys

# Make SSH keys mandatory (disable password auth)
npm run start -- --enable-ssh-keys --disallow-user-password
```

#### 4. No Authentication
For trusted environments only:
```bash
npm run start -- --no-auth
```

#### 5. Local Bypass (Development Only)
Allow localhost connections to bypass authentication:
```bash
# Basic local bypass (DEVELOPMENT ONLY - NOT FOR PRODUCTION)
npm run start -- --allow-local-bypass

# With token for additional security (minimum for production)
npm run start -- --allow-local-bypass --local-auth-token mytoken
```

**Security Note**: Local bypass uses `req.socket.remoteAddress` which cannot be spoofed remotely due to TCP's three-way handshake. The implementation also rejects requests with proxy headers (`X-Forwarded-For`, etc.) to prevent header injection attacks. However:
- **Development only**: Basic bypass without token should never be used in production
- **Local processes**: Any process on the same machine can access the API
- **Always use tokens**: In production, always require `--local-auth-token`
- **Consider alternatives**: For production, use proper authentication instead of local bypass

### macOS App Authentication

The macOS menu bar app supports these authentication modes:
- **No Authentication**: For trusted environments only
- **System Authentication**: Uses your macOS user account credentials
- **SSH Key Authentication**: Uses Ed25519 SSH keys from `~/.ssh/authorized_keys`
- Configure via Settings → Security when in "Network" mode

### Security Best Practices

1. **Always use authentication** when binding to network interfaces (`--bind 0.0.0.0`)
2. **Use HTTPS** in production with a reverse proxy (nginx, Caddy)
3. **Rotate credentials** regularly
4. **Consider SSH keys** for stronger security
5. **Never use local bypass without tokens** in production environments
6. **Monitor access logs** for suspicious authentication patterns
7. **Default to secure** - explicitly enable less secure options only when needed

### SSH Key Authentication Troubleshooting

If SSH key generation fails with crypto errors, see the [detailed troubleshooting guide](web/README.md#ssh-key-authentication-issues) for solutions.


## npm Package

The VibeTunnel npm package provides the full server functionality for Linux, Docker, CI/CD environments, and headless macOS systems.

### Installation

```bash
# Install globally via npm
npm install vibetunnel

# Or with yarn
yarn global add vibetunnel

# Or with pnpm
pnpm add vibetunnel
```

**Requirements**: Node.js 22.12 through 24.x

### One-shot (no install)

```bash
# Start local server (no auth)
npx -y vibetunnel --no-auth

# Quick remote share (ngrok)
npx -y vibetunnel --no-auth --ngrok

# One-shot vt wrapper
npx -y --package vibetunnel vt npm test
```

### Running the VibeTunnel Server

#### Basic Usage

```bash
# Start with default settings (localhost:4020)
vibetunnel

# Bind to all network interfaces
vibetunnel --bind 0.0.0.0

# Use a custom port
vibetunnel --port 8080

# With authentication
VIBETUNNEL_USERNAME=admin VIBETUNNEL_PASSWORD=secure vibetunnel --bind 0.0.0.0

# Enable debug logging
VIBETUNNEL_DEBUG=1 vibetunnel

# Run without authentication (trusted networks only!)
vibetunnel --no-auth
```

#### Using the `vt` Command

The `vt` command wrapper makes it easy to forward terminal sessions:

```bash
# Run any command and see it in the browser
vt npm test
vt python script.py
vt cargo build --release

# Open an interactive shell
vt --shell
vt -i  # short form

# Control terminal titles
vt --title-mode static npm run dev     # Shows path and command
vt --title-mode filter vim             # Blocks vim from changing title

# Control output verbosity
vt -q npm test         # Quiet mode - no console output
vt -v npm run dev      # Verbose mode - show more information
vt -vv cargo build     # Extra verbose - all except debug
vt -vvv python app.py  # Debug mode - show everything

# Shell aliases work automatically!
vt claude-danger  # Your custom alias for claude --dangerously-skip-permissions

# Update session title (inside a VibeTunnel session)
vt title "My Project - Testing"
```

### The `vt` Forwarding Command

The `vt` command is VibeTunnel's terminal forwarding wrapper that allows you to run any command while making its output visible in the browser. Under the hood, `vt` is a convenient shortcut for `vibetunnel fwd` - it's a bash script that calls the full command with proper path resolution and additional features like shell alias support. The `vt` wrapper acts as a transparent proxy between your terminal and the command, forwarding all input and output through VibeTunnel's infrastructure.

#### Command Syntax

```bash
vt [options] <command> [args...]
```

#### Options

**Terminal Title Control:**
- `--title-mode <mode>` - Control how terminal titles are managed:
  - `none` - No title management, apps control their own titles (default)
  - `filter` - Block all title changes from applications
  - `static` - Show working directory and command in title

**Verbosity Control:**
- `-q, --quiet` - Quiet mode, no console output (logs to file only)
- `-v, --verbose` - Verbose mode, show errors, warnings, and info messages
- `-vv` - Extra verbose, show all messages except debug
- `-vvv` - Debug mode, show all messages including debug

**Other Options:**
- `--shell, -i` - Launch your current shell interactively
- `--no-shell-wrap, -S` - Execute command directly without shell interpretation
- `--log-file <path>` - Override default log file location (defaults to `~/.vibetunnel/log.txt`)
- `--help, -h` - Show help message with all options

#### Verbosity Levels

VibeTunnel uses a hierarchical logging system where each level includes all messages from more severe levels:

| Level | Flag | Environment Variable | Shows |
|-------|------|---------------------|-------|
| SILENT | `-q` | `VIBETUNNEL_LOG_LEVEL=silent` | No console output (file logging only) |
| ERROR | (default) | `VIBETUNNEL_LOG_LEVEL=error` | Errors only |
| WARN | - | `VIBETUNNEL_LOG_LEVEL=warn` | Errors and warnings |
| INFO | `-v` | `VIBETUNNEL_LOG_LEVEL=info` | Errors, warnings, and informational messages |
| VERBOSE | `-vv` | `VIBETUNNEL_LOG_LEVEL=verbose` | All messages except debug |
| DEBUG | `-vvv` | `VIBETUNNEL_LOG_LEVEL=debug` | Everything including debug traces |

**Note:** All logs are always written to `~/.vibetunnel/log.txt` regardless of verbosity settings. The verbosity only controls terminal output.

#### Examples

```bash
# Basic command forwarding
vt ls -la                    # List files with VibeTunnel monitoring
vt npm run dev              # Run development server
vt python script.py         # Execute Python script

# With verbosity control
vt -q npm test              # Run tests silently
vt -v npm install           # See detailed installation progress
vt -vvv python debug.py     # Full debug output
vt --log-file debug.log npm run dev  # Write logs to custom file

# Terminal title management
vt --title-mode static npm run dev     # Fixed title showing command
vt --title-mode filter vim             # Prevent vim from changing title

# Shell handling
vt --shell                  # Open interactive shell
vt -S /usr/bin/python      # Run python directly without shell
```

#### How It Works

1. **Command Resolution**: The `vt` wrapper first checks if your command is an alias, shell function, or binary
2. **Session Creation**: It creates a new VibeTunnel session with a unique ID
3. **PTY Allocation**: A pseudo-terminal is allocated to preserve terminal features (colors, cursor control, etc.)
4. **I/O Forwarding**: All input/output is forwarded between your terminal and the browser in real-time
5. **Process Management**: The wrapper monitors the process and handles signals, exit codes, and cleanup

#### Environment Variables

- `VIBETUNNEL_LOG_LEVEL` - Set default verbosity level (silent, error, warn, info, verbose, debug)
- `VIBETUNNEL_TITLE_MODE` - Set default title mode (none, filter, static)
- `VIBETUNNEL_DEBUG` - Legacy debug flag, equivalent to `VIBETUNNEL_LOG_LEVEL=debug`

#### Special Features

**Shell Alias Support**: Your shell aliases and functions work transparently through `vt`:
```bash
alias gs='git status'
vt gs  # Works as expected
```

**Session Title Updates**: Inside a VibeTunnel session, use `vt title` to update the session name:
```bash
vt title "Building Production Release"
```

### Mac App Interoperability

The npm package is designed to work seamlessly alongside the Mac app:

#### Smart Command Routing
- The `vt` command automatically detects if the Mac app is installed
- If found at `/Applications/VibeTunnel.app`, it defers to the Mac app
- If not found, it uses the npm-installed server
- This ensures you always get the best available implementation

#### Installation Behavior
- If `/usr/local/bin/vt` already exists (from another tool), npm won't overwrite it
- You'll see a helpful warning with alternatives: `vibetunnel` or `npx vt`
- The installation always succeeds, even if the `vt` symlink can't be created

#### When to Use Each Version
- **Mac app only**: Best for macOS users who want menu bar integration
- **npm only**: Perfect for Linux, Docker, CI/CD, or headless servers
- **Both installed**: Mac app takes precedence, npm serves as fallback
- **Development**: npm package useful for testing without affecting Mac app

### Package Contents

The npm package includes:
- Full VibeTunnel server with web UI
- CLI tools (`vibetunnel` and `vt` commands)
- Native PTY support via node-pty
- Pre-built binaries for common platforms
- Complete feature parity with macOS app (minus menu bar)

### Building the npm Package

For maintainers who need to build the npm package:

#### Unified Build (Multi-Platform by Default)
```bash
# Build with prebuilt binaries for all platforms
# Requires Docker for Linux cross-compilation
npm run build:npm
```

This creates prebuilt binaries for:
- macOS (x64, arm64) - Node.js 22, 23, 24
- Linux (x64, arm64) - Node.js 22, 23, 24

#### Build Options
```bash
# Current platform only (faster for development)
node scripts/build-npm.js --current-only

# Specific platform/architecture
node scripts/build-npm.js --platform darwin --arch arm64

# Skip Docker builds
node scripts/build-npm.js --no-docker
```

#### Publishing
```bash
# Test the package locally
npm pack

# Publish to npm
npm publish
```

## Building from Source

### Prerequisites
- macOS 14.0+ (Sonoma) on Apple Silicon (M1+)
- Xcode 16.0+
- Node.js 22.12 through 24.x
- Rustup; `native/vt-fwd/rust-toolchain.toml` installs the pinned Rust toolchain for the native forwarder

### Build Steps

```bash
# Clone the repository
git clone https://github.com/amantus-ai/vibetunnel.git
cd vibetunnel

# Set up code signing (required for macOS/iOS development)
# Create Local.xcconfig files with your Apple Developer Team ID
# Note: These files must be in the same directory as Shared.xcconfig
cat > mac/VibeTunnel/Local.xcconfig << EOF
// Local Development Configuration
// DO NOT commit this file to version control
DEVELOPMENT_TEAM = YOUR_TEAM_ID
CODE_SIGN_STYLE = Automatic
EOF

cat > ios/VibeTunnel/Local.xcconfig << EOF
// Local Development Configuration  
// DO NOT commit this file to version control
DEVELOPMENT_TEAM = YOUR_TEAM_ID
CODE_SIGN_STYLE = Automatic
EOF

# Build the web server
cd web
pnpm install
pnpm run build

# Optional: Build with custom Node.js for smaller binary (46% size reduction)
# export VIBETUNNEL_USE_CUSTOM_NODE=YES
# node build-custom-node.js  # Build optimized Node.js (one-time, ~20 min)
# pnpm run build              # Will use custom Node.js automatically

# Build the macOS app
cd ../mac
./scripts/build.sh --configuration Release
```

### Custom Node.js Builds

VibeTunnel supports building with a custom Node.js for a 46% smaller executable (61MB vs 107MB):

```bash
# Build custom Node.js (one-time, ~20 minutes)
node build-custom-node.js

# Use environment variable for all builds
export VIBETUNNEL_USE_CUSTOM_NODE=YES

# Or use in Xcode Build Settings
# Add User-Defined Setting: VIBETUNNEL_USE_CUSTOM_NODE = YES
```

See [Custom Node Build Flags](docs/custom-node-build-flags.md) for detailed optimization information.

## Development

For development setup and contribution guidelines, see [CONTRIBUTING.md](docs/CONTRIBUTING.md).

### Key Files
- **macOS App**: `mac/VibeTunnel/VibeTunnelApp.swift`
- **Server**: `web/src/server/` (TypeScript/Node.js)
- **Web UI**: `web/src/client/` (Lit/TypeScript)
- **iOS App**: `ios/VibeTunnel/`

### Testing & Code Coverage

VibeTunnel has comprehensive test suites with code coverage enabled for all projects:

```bash
# Run all tests with coverage
./scripts/test-all-coverage.sh

# macOS tests with coverage (Swift Testing)
cd mac && swift test --enable-code-coverage

# iOS tests with coverage (using xcodebuild)
cd ios && ./scripts/test-with-coverage.sh

# Web tests with coverage (Vitest)
cd web && ./scripts/coverage-report.sh
```

**Coverage Requirements**:
- macOS/iOS: 75% minimum (enforced in CI)
- Web: 80% minimum for lines, functions, branches, and statements

### Development Server & Hot Reload

VibeTunnel includes a development server with automatic rebuilding for faster iteration:

#### Development Mode

```bash
cd web
pnpm run dev
```

**What this provides:**
- **Automatic Rebuilds**: esbuild watches for file changes and rebuilds bundles instantly
- **Fast Feedback**: Changes are compiled within seconds of saving
- **Manual Refresh Required**: Browser needs manual refresh to see changes (no hot module replacement)

**How it works:**
- esbuild watch mode detects file changes in `src/`
- Automatically rebuilds JavaScript bundles and CSS
- Express server serves updated files immediately
- Visit `http://localhost:4020` and refresh to see changes

#### Testing on External Devices (iPad, iPhone, etc.)

When developing the web interface, you often need to test changes on external devices to debug browser-specific issues. Here's how to do it:

##### Quick Setup

1. **Run the dev server with network access**:
   ```bash
   cd web
   pnpm run dev --port 4021 --bind 0.0.0.0
   ```
   This binds to all network interfaces, making it accessible from other devices.

2. **Find your Mac's IP address**:
   - System Preferences → Network → Wi-Fi → Details
   - Or run: `ipconfig getifaddr en0`

3. **Access from your external device**:
   ```
   http://[your-mac-ip]:4021
   ```

##### Important Notes

- **Port conflict**: The Mac app runs on port 4020, so use a different port (e.g., 4021) for development
- **Same network**: Ensure both devices are on the same Wi-Fi network
- **Firewall**: macOS may prompt to allow incoming connections - click "Allow"
- **Auto-rebuild**: Changes to the web code are automatically rebuilt, but you need to manually refresh the browser

##### Pasting on Mobile Devices

When using VibeTunnel on mobile browsers (Safari, Chrome), pasting works differently than on desktop:

**To paste on mobile:**
1. Press the paste button on the keyboard toolbar
2. A white input box will appear
3. Long-press inside the white box to bring up the paste menu
4. Select "Paste" from the menu
5. The text will be pasted into your terminal session

**Note**: Due to browser security restrictions on non-HTTPS connections, the paste API is limited on mobile devices. The white input box is a workaround that allows clipboard access through the browser's native paste functionality.

#### Future: Hot Module Replacement

For true hot module replacement without manual refresh, see our [Vite migration plan](docs/vite-plan.md) which would provide:
- Instant updates without page refresh
- Preserved application state during development
- Sub-second feedback loops
- Modern development tooling

#### Mac App Development Server Mode

The VibeTunnel Mac app includes a special development server mode that integrates with the web development workflow:

**Setup:**
1. Open VibeTunnel Settings → Debug tab (enable Debug Mode first in General settings)
2. Enable "Use Development Server"
3. Set the path to your `web/` directory
4. Restart the VibeTunnel server

**How it works:**
- Instead of using the bundled production server, the Mac app runs `pnpm run dev` in your web directory
- Provides hot reload and automatic rebuilding during development
- Maintains all Mac app functionality (session management, logging, etc.)
- Shows "Dev Server" in the menu bar and status indicators

**Benefits:**
- No need to manually rebuild after code changes
- Automatic esbuild watch mode for instant compilation
- Full integration with Mac app features
- Same terminal session management as production

**Alternative: Standalone Development**

If you prefer working outside the Mac app:

1. Build the web project: `cd web && pnpm run build`
2. In VibeTunnel settings, set Dashboard Access to "Network"
3. Access from external device: `http://[your-mac-ip]:4020`

Note: This requires rebuilding after each change, so the dev server mode above is preferred for rapid iteration.

### Debug Logging

Enable debug logging for troubleshooting:

```bash
# Enable debug mode
export VIBETUNNEL_DEBUG=1

# Or use inline
VIBETUNNEL_DEBUG=1 vt your-command
```

Debug logs are written to `~/.vibetunnel/log.txt`.

### Using Development Builds with vt

When developing VibeTunnel, you can use the `VIBETUNNEL_PREFER_DERIVED_DATA` environment variable to make the `vt` command prefer development builds from Xcode's DerivedData folder:

```bash
# Enable DerivedData preference
export VIBETUNNEL_PREFER_DERIVED_DATA=1

# vt will now search for and use the latest VibeTunnel build from DerivedData
vt your-command
```

When this environment variable is set, `vt` will:
1. First search for VibeTunnel builds in `~/Library/Developer/Xcode/DerivedData`
2. Use the most recently modified build found there
3. Fall back to `/Applications/VibeTunnel.app` if no DerivedData build exists
4. Log the exact binary location, version, and build timestamp being used

This is particularly useful for:
- Testing changes without installing to `/Applications`
- Working with multiple VibeTunnel builds simultaneously
- Quickly switching between development and production versions
- Debugging which version of VibeTunnel is being used

The version information is also:
- Stored in `session.json` for each session
- Displayed in `vt status` output
- Shown in the initial log output when `VIBETUNNEL_PREFER_DERIVED_DATA` is set

### Verbosity Control

Control the amount of output from VibeTunnel commands:

```bash
# Command-line flags
vt -q npm test                # Quiet mode - no console output
vt npm test                   # Default - errors only
vt -v npm run dev            # Verbose - errors, warnings, and info
vt -vv cargo build           # Extra verbose - all except debug
vt -vvv python script.py     # Debug mode - everything

# Environment variable
export VIBETUNNEL_LOG_LEVEL=error    # Default
export VIBETUNNEL_LOG_LEVEL=warn     # Show errors and warnings
export VIBETUNNEL_LOG_LEVEL=info     # Show errors, warnings, and info
export VIBETUNNEL_LOG_LEVEL=verbose  # All except debug
export VIBETUNNEL_LOG_LEVEL=debug    # Everything

# Or use inline
VIBETUNNEL_LOG_LEVEL=silent vt npm test
```

**Note**: All logs are always written to `~/.vibetunnel/log.txt` regardless of verbosity level. The verbosity settings only control what's displayed in the terminal.

## Poltergeist Integration

[Poltergeist](https://github.com/steipete/poltergeist) is an intelligent file watcher and auto-builder that can automatically rebuild VibeTunnel as you develop. This is particularly useful for native app development where manual rebuilds can interrupt your flow.

### Setting Up Poltergeist

1. **Install Poltergeist** (if not already installed):
   ```bash
   npm install poltergeist
   ```

2. **Start Poltergeist** in the VibeTunnel directory:
   ```bash
   cd /path/to/vibetunnel
   poltergeist
   ```

3. **Make changes** - Poltergeist will automatically rebuild when it detects changes to:
   - Swift files in `mac/` or `ios/`
   - Xcode project files
   - Configuration files

### Poltergeist Features

- **Automatic Rebuilds**: Detects file changes and rebuilds instantly
- **Smart Debouncing**: Prevents excessive rebuilds during rapid edits
- **Build Notifications**: macOS notifications for build success/failure
- **Menu Bar Integration**: Monitor build status from the macOS menu bar
- **Parallel Builds**: Can build macOS and iOS targets simultaneously

### Configuration

VibeTunnel includes a `poltergeist.config.json` that configures:
- **vibetunnel** target: Builds the macOS app in Debug configuration
- **vibetunnel-ios** target: Builds the iOS app (disabled by default)

To enable iOS builds, edit `poltergeist.config.json` and set `"enabled": true` for the vibetunnel-ios target.

## Documentation

- [Keyboard Shortcuts](docs/keyboard-shortcuts.md) - Complete keyboard shortcut reference
- [Quick Reference Card](docs/keyboard-shortcuts-quick-reference.md) - Printable shortcuts cheat sheet
- [Technical Specification](docs/spec.md) - Detailed architecture and implementation
- [Contributing Guide](docs/CONTRIBUTING.md) - Development setup and guidelines
- [Architecture](docs/architecture.md) - System design overview
- [Build System](docs/build-system.md) - Build process details
- [Push Notifications](docs/push-notification.md) - How web push notifications work

## macOS Permissions

macOS is finicky when it comes to permissions. The system will only remember the first path from where an app requests permissions. If subsequently the app starts somewhere else, it will silently fail. Fix: Delete the entry and restart settings, restart app and next time the permission is requested, there should be an entry in Settings again.

Important: You need to set your Developer ID in Local.xcconfig. If apps are signed Ad-Hoc, each new signing will count as a new app for macOS and the permissions have to be (deleted and) requested again.

**Debug vs Release Bundle IDs**: The Debug configuration uses a different bundle identifier (`sh.vibetunnel.vibetunnel.debug`) than Release (`sh.vibetunnel.vibetunnel`). This allows you to have both versions installed simultaneously, but macOS treats them as separate apps for permissions. You'll need to grant permissions separately for each version.

If that fails, use the terminal to reset:

```
# This removes Accessibility permission for a specific bundle ID:
sudo tccutil reset Accessibility sh.vibetunnel.vibetunnel
sudo tccutil reset Accessibility sh.vibetunnel.vibetunnel.debug  # For debug builds

sudo tccutil reset ScreenCapture sh.vibetunnel.vibetunnel
sudo tccutil reset ScreenCapture sh.vibetunnel.vibetunnel.debug  # For debug builds

# This removes all Automation permissions system-wide (cannot target specific apps):
sudo tccutil reset AppleEvents
```

## Logging and Privacy

VibeTunnel uses Apple's unified logging system with the subsystem `sh.vibetunnel.vibetunnel`. By default, macOS redacts sensitive runtime data in logs, showing `<private>` instead of actual values. This is a privacy feature to prevent accidental exposure of sensitive information.

### Bundle Identifiers

VibeTunnel uses the following bundle identifiers:

**Production:**
- `sh.vibetunnel.vibetunnel` - Main macOS app and logging subsystem
- `sh.vibetunnel.vibetunnel.debug` - Debug builds of the macOS app

**Testing:**
- `sh.vibetunnel.vibetunnel.tests` - macOS test suite
- `sh.vibetunnel.ios.tests` - iOS test suite

**iOS:**
- `sh.vibetunnel.ios` - iOS keychain service and URL scheme

### Viewing Unredacted Logs

To see full log details for debugging, you have several options:

1. **Install the Configuration Profile** (Recommended - easiest method):
   ```bash
   # Install the logging configuration profile
   open apple/logging/VibeTunnel-Logging.mobileconfig
   
   # This enables debug logging for all VibeTunnel components
   # To remove later: System Settings → Privacy & Security → Profiles
   ```

2. **Use the vtlog script** (convenient log viewer):
   ```bash
   # View recent logs (requires configuration profile or sudo)
   ./scripts/vtlog.sh
   
   # Follow logs in real-time
   ./scripts/vtlog.sh -f
   
   # Show only errors
   ./scripts/vtlog.sh -e
   
   # Filter by category
   ./scripts/vtlog.sh -c ServerManager
   ```

3. **Configure passwordless sudo** (alternative for vtlog with -p flag):
   ```bash
   # Add to sudoers (replace 'yourusername' with your actual username)
   sudo visudo
   # Add this line:
   yourusername ALL=(ALL) NOPASSWD: /usr/bin/log
   
   # Then use vtlog with private flag
   ./scripts/vtlog.sh -p
   ```

4. **Enable private data logging** using a plist file (alternative):
   ```bash
   # Create the plist to enable private data for VibeTunnel
   sudo mkdir -p /Library/Preferences/Logging/Subsystems
   sudo tee /Library/Preferences/Logging/Subsystems/sh.vibetunnel.vibetunnel.plist > /dev/null << 'EOF'
   <?xml version="1.0" encoding="UTF-8"?>
   <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
   <plist version="1.0">
   <dict>
       <key>Enable-Private-Data</key>
       <true/>
   </dict>
   </plist>
   EOF
   ```

### Why Logs Show `<private>`

Apple redacts dynamic values in logs by default to protect user privacy. This prevents accidental logging of passwords, tokens, or personal information. Our configuration profile or the methods above enable full visibility for development and debugging.

For more detailed information about logging privacy and additional methods, see [apple/docs/logging-private-fix.md](apple/docs/logging-private-fix.md).

## Contributing

We welcome contributions! VibeTunnel is a community-driven project and we'd love to have you join us.

### Join Our Community

Connect with the VibeTunnel team and other contributors on our [Discord server](https://discord.gg/3Ub3EUwrcR). It's the best place to:
- Discuss new features and ideas
- Get help with development setup
- Coordinate on larger changes
- Share your VibeTunnel use cases

### How to Contribute

1. **Join Discord**: Start by joining our [Discord server](https://discord.gg/3Ub3EUwrcR) to say hello!
2. **Check Issues**: Look for issues labeled `good first issue` or `help wanted`
3. **Development Setup**: Follow our [Contributing Guide](docs/CONTRIBUTING.md) for detailed setup instructions
4. **Submit PRs**: Fork the repo, create a branch, and submit your changes

For technical details on building and developing VibeTunnel, see our [Contributing Guide](docs/CONTRIBUTING.md).

## Support VibeTunnel

Love VibeTunnel? Help us keep the terminal vibes flowing! Your support helps us buy pizza and drinks while we keep hacking on your favorite AI agent orchestration platform.

All donations go directly to the development team. Choose your own amount - one-time or monthly! Visit our [Polar page](https://vibetunnel.sh/#support) to support us.

## Credits

Created with ❤️ by:
- [@badlogic](https://mariozechner.at/) - Mario Zechner
- [@mitsuhiko](https://lucumr.pocoo.org/) - Armin Ronacher  
- [@steipete](https://steipete.com/) - Peter Steinberger
- [@hjanuschka](https://x.com/hjanuschka) - Helmut Januschka
- [@manuelmaly](https://x.com/manuelmaly) - Manuel Maly

## License

VibeTunnel is open source software licensed under the MIT License. See [LICENSE](LICENSE) for details.

---

**Ready to vibe?** [Download VibeTunnel](https://github.com/amantus-ai/vibetunnel/releases/latest) and start tunneling!

---

### Overview (`ios/README.md`)

# VibeTunnel iOS

🚀 Beautiful native iOS/iPadOS client for VibeTunnel terminal multiplexer with a modern, terminal-inspired design.

## ✨ Features

- **Native SwiftUI app** optimized for iOS 18+
- **Beautiful terminal-inspired UI** with custom theme and animations
- **Full terminal emulation** using ghostty-web (WASM + canvas)
- **Real-time session management** with WebSocket buffer streaming
- **Keyboard toolbar** with special keys (arrows, ESC, CTRL combinations)
- **OpenAI voice input** with tap-to-record transcription for terminal commands
- **Font size adjustment** with live preview
- **Haptic feedback** throughout the interface
- **Session operations**: Create, kill, cleanup sessions
- **Auto-reconnection** and error handling
- **iPad optimized** (split view support coming soon)

## 🎨 Design Highlights

- Custom dark theme inspired by modern terminal aesthetics
- Smooth animations and transitions
- Glow effects on interactive elements
- Consistent spacing and typography
- Terminal-style monospace fonts throughout

## 📱 Setup Instructions

### 1. Create Xcode Project

1. Open Xcode 16+
2. Create a new project:
   - Choose **iOS** → **App**
   - Product Name: `VibeTunnel`
   - Team: Select your development team
   - Organization Identifier: Your identifier (e.g., `com.yourcompany`)
   - Interface: **SwiftUI**
   - Language: **Swift**
   - Minimum Deployments: **iOS 18.0**
   - Save in the `ios/` directory

### 2. Add Project Files

1. Delete the default `ContentView.swift` and `VibeTunnelApp.swift`
2. Drag the entire `VibeTunnel/` folder into Xcode
3. Choose "Create groups" and ensure "Copy items if needed" is checked
4. Make sure the target membership is set for all files

### 3. Bundle Ghostty Resources

1. Ensure `VibeTunnel/Resources/ghostty/ghostty-web.js` is included in the target
2. Ensure `VibeTunnel/Resources/ghostty/ghostty-vt.wasm` is included in the target
3. These files are vendored from `web/node_modules/ghostty-web/dist`
4. Xcode may flatten the synchronized `ghostty` folder into the app bundle root; the runtime supports both layouts

### 4. Configure Info.plist

1. Replace the auto-generated Info.plist with the one in `Resources/Info.plist`
2. Or manually add:
   ```xml
   <key>NSAppTransportSecurity</key>
   <dict>
       <key>NSAllowsArbitraryLoads</key>
       <true/>
   </dict>
   ```

### 5. (Optional) Add Custom Fonts

For the best experience, add Fira Code font:
1. Download [Fira Code](https://github.com/tonsky/FiraCode)
2. Add `.ttf` files to the project
3. Ensure they're included in the target
4. The Info.plist already includes font references

### 6. Build and Run

#### Using Xcode
1. Select your device or simulator (iOS 18+)
2. Press **⌘R** to build and run
3. The app will launch with the beautiful connection screen

#### Using xcodebuildmcp
```bash
# Build the app
xcodebuildmcp build -workspace ../VibeTunnel.xcworkspace -scheme VibeTunnel-iOS

# Run tests
xcodebuildmcp test -workspace ../VibeTunnel.xcworkspace -scheme VibeTunnel-iOS

# Build for device
xcodebuildmcp build -workspace ../VibeTunnel.xcworkspace -scheme VibeTunnel-iOS -destination "generic/platform=iOS"
```

## 🏗️ Architecture

```
VibeTunnel/
├── App/                    # App entry point and main views
├── Models/                 # Data models (Session, ServerConfig, etc.)
├── Views/                  # UI Components
│   ├── Connection/        # Server connection flow
│   ├── Sessions/          # Session list and management
│   ├── Terminal/          # Terminal emulator integration
│   └── Common/            # Reusable components
├── Services/              # Networking and API
│   ├── APIClient          # HTTP client for REST API
│   ├── SessionService     # Session management logic
│   └── BufferWebSocketClient # Binary buffer streaming
├── Utils/                 # Helpers and extensions
│   └── Theme.swift        # Design system and styling
└── Resources/             # Assets and configuration
```

## 🚦 Usage

1. **Connect to Server**
   - Enter your VibeTunnel server IP/hostname
   - Default port is 3000
   - Optionally name your connection

2. **Manage Sessions**
   - Tap **+** to create new session
   - Choose command (zsh, bash, python3, etc.)
   - Set working directory
   - Name your session (optional)

3. **Use Terminal**
   - Full terminal emulation with ghostty-web
   - Special keys toolbar for mobile input
   - Add an OpenAI API key under Settings → General → Voice Input, then tap the microphone in the keyboard toolbar to insert transcribed text
   - Pinch to zoom or use menu for font size
   - Long press for copy/paste

4. **Session Actions**
   - Swipe or long-press for context menu
   - Kill running sessions
   - Clean up exited sessions
   - Batch cleanup available

## 🛠️ Development Notes

- **Minimum iOS**: 18.0 (uses latest SwiftUI features)
- **Swift**: 6.0 compatible
- **Dependencies**: ghostty-web resources for terminal emulation
- **Terminal transport**: interactive terminals stream stdout for low-latency echo; session previews use canonical snapshots
- **Architecture**: MVVM with SwiftUI and Combine

### Logging with vtlog

Monitor app logs in real-time using `vtlog`:

```bash
# Monitor all VibeTunnel logs
vtlog

# Filter for specific components
vtlog | grep BonjourDiscovery
vtlog | grep Logger
vtlog | grep ServerConfig

# Verbose logging
vtlog -v

# Monitor specific subsystem
vtlog --subsystem sh.vibetunnel.ios
```

### Code Quality

```bash
# Format and lint code
./scripts/lint.sh

# Run SwiftFormat only
swiftformat .

# Run SwiftLint only
swiftlint
```

## 🐛 Troubleshooting

- **Connection fails**: Ensure device and server are on same network
- **"Transport security" error**: Check NSAppTransportSecurity in Info.plist
- **Keyboard issues**: The toolbar provides special keys for terminal control
- **Performance**: Adjust font size if rendering is slow on older devices

## 🎯 Future Enhancements

- [ ] iPad split view and multitasking
- [ ] Hardware keyboard shortcuts
- [ ] Session recording and playback
- [ ] Multiple server connections
- [ ] Custom themes
- [ ] File upload/download
- [ ] Session sharing

## 📄 License

Same as VibeTunnel project.

---

**Note**: This is a complete, production-ready iOS app. All core features are implemented including terminal emulation, session management, and a beautiful UI. The only remaining task is iPad-specific optimizations for split view.

---

### Overview (`mac/README.md`)

# VibeTunnel macOS App

This directory contains the macOS version of VibeTunnel.

## Quick Start

### Building
```bash
# Using Xcode
xcodebuild -workspace VibeTunnel.xcworkspace -scheme VibeTunnel build

# Using build script
./scripts/build.sh
```

### Running Tests
```bash
xcodebuild -workspace VibeTunnel.xcworkspace -scheme VibeTunnel test
```

### Creating Release
```bash
./scripts/build.sh --configuration Release
./scripts/create-dmg.sh build/Build/Products/Release/VibeTunnel.app
```

## Project Structure

```
mac/
├── VibeTunnel/           # Source code
│   ├── Core/            # Core services and models
│   ├── Presentation/    # Views and UI components
│   └── Utilities/       # Helper utilities
├── VibeTunnelTests/     # Unit tests
├── scripts/             # Build and release scripts
├── docs/                # macOS-specific documentation
└── private/             # Signing keys (not in git)
```

## Scripts

- `build.sh` - Build the app with optional signing
- `create-dmg.sh` - Create a DMG for distribution
- `release.sh` - Full release process
- `monitor-ci.sh` - Monitor CI build status
- `sign-and-notarize.sh` - Code signing and notarization

## Documentation

See `docs/` for macOS-specific documentation:
- Code signing setup
- Release process
- Sparkle update framework
- Development signing

## CI/CD

The app is built automatically on GitHub Actions:
- On every push to main
- On pull requests
- For releases (tagged with v*)

See `.github/workflows/swift.yml` for the build configuration.

---

### Overview (`web/README.md`)

# VibeTunnel CLI

**Turn any browser into your terminal.** VibeTunnel proxies your terminals right into the browser, so you can vibe-code anywhere.

Full-featured terminal sharing server with web interface for macOS and Linux. Windows not yet supported.

## Why VibeTunnel?

Ever wanted to check on your AI agents while you're away? Need to monitor that long-running build from your phone? Want to share a terminal session with a colleague without complex SSH setups? VibeTunnel makes it happen with zero friction.

## Installation

### From npm (Recommended)
```bash
npm install vibetunnel
```

### From Source
```bash
git clone https://github.com/amantus-ai/vibetunnel.git
cd vibetunnel/web
pnpm install
pnpm run build
```

## Installation Differences

**npm package**:
- Pre-built binaries for common platforms (macOS x64/arm64, Linux x64/arm64)
- Automatic fallback to source compilation if pre-built binaries unavailable
- Global installation makes `vibetunnel` command available system-wide
- Conditional `vt` command installation (see [VT Installation Guide](docs/VT_INSTALLATION.md))
- Includes production dependencies only

**Source installation**:
- Full development environment with hot reload (`pnpm run dev`)
- Access to all development scripts and tools
- Ability to modify and rebuild the application
- Includes test suites and development dependencies

## Requirements

- Node.js >= 22.12.0
- macOS or Linux (Windows not yet supported)
- Build tools for native modules (Xcode on macOS, build-essential on Linux)
- Rustup for source builds; `native/vt-fwd/rust-toolchain.toml` pins the forwarder toolchain

## Usage

### Start the server

```bash
# Start with default settings (port 4020)
vibetunnel

# Start with custom port
vibetunnel --port 8080

# Start without authentication
vibetunnel --no-auth

# Bind to specific interface
vibetunnel --bind 127.0.0.1 --port 4020

# Enable SSH key authentication
vibetunnel --enable-ssh-keys

# SSH keys only (no password auth)
vibetunnel --disallow-user-password
```

Then open http://localhost:4020 in your browser to access the web interface.

### Command-line Options

```
/* Detailed source-code truncated for AI context efficiency. */
```

### Use the vt command wrapper

The `vt` command allows you to run commands with TTY forwarding:

```bash
# Monitor AI agents with automatic activity tracking
vt claude
vt claude --dangerously-skip-permissions

# Run commands with output visible in VibeTunnel
vt npm test
vt python script.py
vt top

# Launch interactive shell
vt --shell
vt -i

# Update session title (inside a session)
vt title "My Project"

# Execute command directly without shell wrapper
vt --no-shell-wrap ls -la
vt -S ls -la

# Control terminal title behavior
vt --title-mode none     # No title management
vt --title-mode filter   # Block all title changes
vt --title-mode static   # Show directory and command

# Verbosity control
vt -q npm test          # Quiet mode (errors only)
vt -v npm run dev       # Verbose mode
vt -vv npm test         # Extra verbose
vt -vvv npm build       # Debug mode
```

### Forward commands to a session

```bash
# Basic usage
vibetunnel fwd <session-id> <command> [args...]

# Examples
vibetunnel fwd --session-id abc123 ls -la
vibetunnel fwd --session-id abc123 npm test
vibetunnel fwd --session-id abc123 python script.py
```

Linux users can install VibeTunnel as a systemd service with `vibetunnel systemd` for automatic startup and process management - see [detailed systemd documentation](docs/systemd.md).

### Environment Variables

VibeTunnel respects the following environment variables:

```bash
PORT=8080                           # Default port if --port not specified
VIBETUNNEL_USERNAME=myuser          # Username (for env-based auth, not CLI)
VIBETUNNEL_PASSWORD=mypass          # Password (for env-based auth, not CLI)
JWT_SECRET=long-random-secret        # Optional JWT signing key override
VIBETUNNEL_CONTROL_DIR=/path        # Control directory for session data
VIBETUNNEL_SESSION_ID=abc123        # Current session ID (set automatically inside sessions)
VIBETUNNEL_LOG_LEVEL=debug          # Log level: error, warn, info, verbose, debug
PUSH_CONTACT_EMAIL=admin@example.com # Contact email for VAPID configuration
```

## Tailscale Integration

VibeTunnel supports Tailscale Serve and Funnel for secure remote access:

### Tailscale Serve (Private Access)
Enable HTTPS access within your Tailnet:
```bash
# Start with Tailscale Serve enabled
vibetunnel --enable-tailscale-serve

# Access via HTTPS within your Tailnet
https://your-machine-name
```

**Note**: Mobile browsers may reject Tailscale's self-signed certificates in Private mode. The server will fallback to showing HTTP URLs with IP addresses for mobile access.

### Tailscale Funnel (Public Access)
Enable public internet access with valid SSL certificates:
```bash
# Start with both Serve and Funnel enabled
vibetunnel --enable-tailscale-serve --enable-tailscale-funnel

# Access from anywhere on the internet
https://your-machine-name.tail-scale.ts.net
```

### Requirements
- Tailscale must be installed and running
- For Funnel: Your Tailscale account must have Funnel enabled
- The server automatically configures Tailscale Serve/Funnel on startup

## Features

- **Web-based terminal interface** - Access terminals from any browser
- **Multiple concurrent sessions** - Run multiple terminals simultaneously
- **Real-time synchronization** - See output in real-time
- **TTY forwarding** - Full terminal emulation support
- **Session management** - Create, list, and manage sessions
- **Git worktree support** - Work on multiple branches simultaneously
- **Cross-platform** - Works on macOS and Linux
- **No dependencies** - Just Node.js required

### Git Worktree Integration

VibeTunnel provides comprehensive Git worktree support, allowing you to:
- Work on multiple branches simultaneously without stashing changes
- Create new worktrees directly from the session creation dialog
- Smart branch switching with uncommitted change detection
- Follow mode to keep multiple worktrees in sync
- Visual indicators for worktree sessions

For detailed information, see the [Git Worktree Management Guide](docs/worktree.md).

## Package Contents

This npm package includes:
- Full VibeTunnel server with web UI
- Command-line tools (vibetunnel, vt)
- Native PTY support for terminal emulation
- Web interface with ghostty-web
- Session management and forwarding
- Built-in systemd service management for Linux

## Platform Support

- macOS (Intel and Apple Silicon)
- Linux (x64 and ARM64)
- Windows: Not yet supported ([#252](https://github.com/amantus-ai/vibetunnel/issues/252))

## Troubleshooting

### Installation Issues

If you encounter issues during installation:

1. **Missing Build Tools**: Install build essentials
   ```bash
   # Ubuntu/Debian
   sudo apt-get install build-essential python3-dev
   
   # macOS
   xcode-select --install
   ```

2. **Permission Issues**: Use sudo for global installation
   ```bash
   sudo npm install vibetunnel
   ```

3. **Node Version**: Ensure Node.js 22.12 through 24.x is installed
   ```bash
   node --version
   ```

### Runtime Issues

- **Server Won't Start**: Check if port is already in use
- **Authentication Failed**: Verify system authentication setup
- **Terminal Not Responsive**: Check browser console for WebSocket errors

### SSH Key Authentication Issues

If you encounter errors when generating or importing SSH keys (e.g., "Cannot read properties of undefined"), this is due to browser security restrictions on the Web Crypto API.

#### The Issue
Modern browsers (Chrome 60+, Firefox 75+) block the Web Crypto API when accessing web applications over HTTP from non-localhost addresses. This affects:
- Local network IPs (192.168.x.x, 10.x.x.x, 172.16-31.x.x)
- Any non-localhost hostname over HTTP

#### Solutions

1. **Use localhost (Recommended)**
   ```bash
   # Access VibeTunnel via localhost
   http://localhost:4020
   
   # If running on a remote server, use SSH tunneling:
   ssh -L 4020:localhost:4020 user@your-server
   # Then access http://localhost:4020 in your browser
   ```

2. **Enable HTTPS**
   Set up a reverse proxy with HTTPS using nginx or Caddy (recommended for production).

3. **Chrome Flag Workaround** (Development only)
   - Navigate to `chrome://flags/#unsafely-treat-insecure-origin-as-secure`
   - Add your server URL (e.g., `http://192.168.1.100:4020`)
   - Enable the flag and restart Chrome
   - ⚠️ This reduces security - use only for development

#### Why This Happens
The Web Crypto API is restricted to secure contexts (HTTPS or localhost) to prevent man-in-the-middle attacks on cryptographic operations. This is a browser security feature, not a VibeTunnel limitation.

### Development Setup

For source installations:
```bash
# Install dependencies
pnpm install

# Run development server with hot reload
pnpm run dev

# Run code quality checks
pnpm run check

# Build for production
pnpm run build
```

## Documentation

See the main repository for complete documentation: https://github.com/amantus-ai/vibetunnel

## License

MIT

---

### Overview (`.github/workflows/README.md`)

# VibeTunnel CI/CD Workflows

This directory contains GitHub Actions workflows for continuous integration and testing.

## Workflows

### 1. Web CI (`web-ci.yml`)
Basic CI workflow that runs on every push and PR affecting the web directory.

**Jobs:**
- **Lint and Type Check**: Runs biome linting and TypeScript type checking
- **Build**: Builds the project and uploads artifacts
- **Test**: Runs the test suite

**Triggers:**
- Push to `main` or `ms-pty` branches
- Pull requests to `main`
- Only when files in `web/` directory change

### 2. NPM Package Test (`npm-test.yml`)
Dedicated workflow for testing the npm package build and installation.

**Jobs:**
- **Test NPM Package**: Builds the npm package and tests installation in a clean environment

**Features:**
- Builds npm package using the clean build approach
- Tests global installation without requiring build tools
- Verifies server startup and API functionality
- Validates package structure and dependencies

**Triggers:**
- Push to `main` or `npm-build` branches when web files change
- Pull requests to `main` when web files change

### 3. SEA Build Test (`sea-build-test.yml`)
Advanced workflow for testing Single Executable Application (SEA) builds with custom Node.js.

**Features:**
- Builds custom Node.js from source with optimizations
- Uses Blacksmith runners for significantly faster builds
- Caches custom Node.js builds for faster subsequent runs
- Tests SEA builds with both system and custom Node.js
- Supports manual triggers with custom Node.js versions

**Jobs:**
1. **build-custom-node**: 
   - Runs on `blacksmith-32vcpu-ubuntu-2404-arm` for fast compilation
   - Builds minimal Node.js without npm, intl, inspector, etc.
   - Uses Blacksmith cache for persistence
   - Outputs the custom Node.js path for downstream jobs

2. **test-sea-build**:
   - Runs on `blacksmith-8vcpu-ubuntu-2404-arm`
   - Matrix build testing both system and custom Node.js
   - Builds SEA executable with node-pty patches
   - Performs smoke tests on the generated executable
   - Uploads artifacts for inspection

3. **test-github-runners**:
   - Uses standard `ubuntu-latest` runners for comparison
   - Helps identify any Blacksmith-specific issues
   - Runs only on push events

### 4. Xcode SEA Test (`xcode-sea-test.yml`)
Tests the macOS Xcode build with custom Node.js to ensure the VibeTunnel.app works correctly with SEA executables.

**Features:**
- Builds custom Node.js on macOS using self-hosted runners
- Tests integration of SEA executable into macOS app bundle
- Verifies the app launches and contains the correct binaries
- Supports manual triggers with custom Node.js versions

**Jobs:**
1. **build-custom-node-mac**:
   - Runs on self-hosted macOS runner
   - Builds custom Node.js for macOS
   - Uses GitHub Actions cache (appropriate for self-hosted)
   - Outputs node path and size information

2. **test-xcode-build**:
   - Builds SEA executable with custom Node.js
   - Copies SEA and native modules to app resources
   - Builds VibeTunnel.app using Xcode
   - Verifies SEA executable is correctly bundled
   - Tests basic app functionality
   - Uploads built app as artifact

## Runner Strategy

### Blacksmith Runners (Linux)
- **Custom Node.js Build**: `blacksmith-32vcpu-ubuntu-2404-arm` (high CPU for compilation)
- **Other CI Jobs**: `blacksmith-8vcpu-ubuntu-2404-arm` (standard workloads)
- Benefits: Significantly faster builds, better caching, ARM64 architecture

### Self-Hosted Runners (macOS)
- Used for Xcode builds and macOS-specific testing
- Access to Xcode and macOS-specific tools
- Can test code signing and notarization

### GitHub Runners (Comparison)
- `ubuntu-latest` used in test job for baseline comparison
- Helps identify Blacksmith-specific issues

## Caching Strategy

### Blacksmith Cache
**IMPORTANT**: When using Blacksmith runners, you MUST use `useblacksmith/cache@v1`
- Used for all jobs running on Blacksmith runners
- Provides faster cache operations
- Better persistence than GitHub Actions cache
- Cache key: `custom-node-linux-x64-v{version}-{hash}`

### GitHub Actions Cache
**Only used for self-hosted runners and standard GitHub runners**
- Self-hosted macOS runners use `actions/cache@v4`
- Standard GitHub runners use `actions/cache@v4`
- Cache key format same as Blacksmith

### Cache Performance
- Initial custom Node.js build: ~10-15 minutes on 32vCPU
- Cached builds: ~1 minute
- Blacksmith cache restoration: 2-3x faster than GitHub Actions cache

## Manual Triggers

The SEA build workflow supports manual triggers via GitHub UI:
```yaml
workflow_dispatch:
  inputs:
    node_version:
      description: 'Node.js version to build'
      default: '24.2.0'
```

## Local Testing

To test the SEA build locally:
```bash
# Build custom Node.js
cd web
node build-custom-node.js

# Build SEA with custom Node.js
node build-native.js --custom-node=".node-builds/node-v24.2.0-minimal/out/Release/node"
```

## Optimization Details

The custom Node.js build reduces:
- ICU locale data (`--with-intl=small-icu`)
- npm and corepack (`--without-npm --without-corepack`)
- Inspector/debugging (`--without-inspector`)
- Code cache and snapshots
- Uses `-Os` optimization for size

This reduces the Node.js binary from ~110MB to ~68MB.

## Future Improvements

- [ ] Add Windows and macOS to the build matrix
- [ ] Implement release workflow for automated releases
- [ ] Add performance benchmarks
- [ ] Integrate with release signing process

---

### Overview (`apple/logging/README.md`)

# VibeTunnel Logging Configuration Profile

This directory contains the configuration profile for enabling full debug logging in VibeTunnel apps.

## What It Does

The `VibeTunnel-Logging.mobileconfig` profile enables:
- Debug-level logging for both macOS and iOS apps
- Visibility of private data (no more `<private>` tags)
- Persistent logging at debug level

## Installation

### macOS
1. Double-click `VibeTunnel-Logging.mobileconfig`
2. System Settings will open
3. Go to Privacy & Security → Profiles
4. Click on "VibeTunnel Debug Logging" 
5. Click "Install..."
6. Enter your password when prompted
7. Restart VibeTunnel for changes to take effect

### iOS
1. AirDrop or email the `VibeTunnel-Logging.mobileconfig` to your iOS device
2. Tap the file to open it
3. iOS will prompt to review the profile
4. Go to Settings → General → VPN & Device Management
5. Tap on "VibeTunnel Debug Logging"
6. Tap "Install" and enter your passcode
7. Restart the VibeTunnel app

## Verification

After installation, logs should show full details:
```bash
# macOS - using vtlog script
./scripts/vtlog.sh

# iOS - in Xcode console or Console.app
# You should see actual values instead of <private>
```

## Removal

### macOS
1. System Settings → Privacy & Security → Profiles
2. Select "VibeTunnel Debug Logging"
3. Click the minus (-) button
4. Confirm removal

### iOS
1. Settings → General → VPN & Device Management
2. Tap "VibeTunnel Debug Logging"
3. Tap "Remove Profile"
4. Enter passcode to confirm

## Security Note

This profile enables detailed logging which may include sensitive information. Only install on development devices and remove when no longer needed for debugging.

## Technical Details

The profile configures logging for all VibeTunnel subsystems:

### macOS
- `sh.vibetunnel.vibetunnel` - Main macOS app and all components
- `sh.vibetunnel.vibetunnel.debug` - Debug builds
- `sh.vibetunnel.vibetunnel.tests` - Test suite
- `sh.vibetunnel.vibetunnel.tests.debug` - Debug test builds

### iOS
- `sh.vibetunnel.ios` - Main iOS app and all components
- `sh.vibetunnel.ios.tests` - iOS test suite

All subsystems are configured to:
- Enable at Debug level
- Persist at Debug level
- Show private data (no `<private>` redaction)

---

### Overview (`ios/VibeTunnelTests/README.md`)

# VibeTunnel iOS Tests

This directory contains the test suite for the VibeTunnel iOS application using Swift Testing framework.

## Test Structure

```
VibeTunnelTests/
├── Mocks/                   # Mock implementations for testing
│   ├── MockAPIClient.swift
│   ├── MockURLProtocol.swift
│   └── MockWebSocketTask.swift
├── Services/                # Service layer tests
│   ├── APIClientTests.swift
│   ├── BufferWebSocketClientTests.swift
│   └── ConnectionManagerTests.swift
├── Models/                  # Data model tests
│   ├── SessionTests.swift
│   └── ServerConfigTests.swift
├── Utilities/              # Test utilities
│   ├── TestFixtures.swift
│   └── TestTags.swift
└── Integration/            # Integration tests (future)
```

## Running Tests

### Command Line
```bash
cd ios
swift test
```

### Xcode
1. Open `VibeTunnel.xcodeproj`
2. Select the VibeTunnel scheme
3. Press `Cmd+U` or choose Product → Test

### CI
Tests run automatically in GitHub Actions on every push and pull request.

## Test Tags

Tests are organized with tags for selective execution:
- `@Tag.critical` - Core functionality tests
- `@Tag.networking` - Network-related tests
- `@Tag.websocket` - WebSocket functionality
- `@Tag.models` - Data model tests
- `@Tag.persistence` - Data persistence tests
- `@Tag.integration` - Integration tests

Run specific tags:
```bash
swift test --filter .critical
swift test --filter .networking
```

## Writing Tests

This project uses Swift Testing (not XCTest). Key differences:
- Use `@Test` attribute instead of `test` prefix
- Use `#expect()` instead of `XCTAssert`
- Use `@Suite` to group related tests
- Tests run in parallel by default

Example:
```swift
@Suite("MyFeature Tests", .tags(.critical))
struct MyFeatureTests {
    @Test("Does something correctly")
    func testFeature() async throws {
        // Arrange
        let sut = MyFeature()
        
        // Act
        let result = try await sut.doSomething()
        
        // Assert
        #expect(result == expectedValue)
    }
}
```

## Coverage Goals

- APIClient: 90%+
- BufferWebSocketClient: 85%+
- Models: 95%+
- Overall: 80%+

---

### Overview (`web/node-pty/README.md`)

# Vendored PTY

This is a vendored fork of [node-pty](https://github.com/microsoft/node-pty) v1.1.0-beta34 with the threading and shared pipe architecture removed.

## Why?

The original node-pty uses a shared pipe/socket architecture through `ConoutSocketWorker` that causes issues when used heavily:
- All PTY instances write to the same shared pipe
- This can overwhelm other Electron processes (like VS Code) that are also listening on the pipe
- Heavy usage from VibeTunnel causes crashes in other applications

## What's Changed?

1. **Removed ConoutSocketWorker** - No more worker threads for socket management
2. **Removed shared pipe architecture** - Each PTY instance uses direct file descriptors
3. **Simplified Windows implementation** - Direct socket connections without intermediary workers
4. **Kept core functionality** - The essential PTY spawn/resize/kill operations remain unchanged

## Building

```bash
npm install
npm run build
```

The native modules will be compiled during installation.

## Usage

The API remains compatible with node-pty:

```javascript
const pty = require('node-pty');
const ptyProcess = pty.spawn('bash', [], {
  name: 'xterm-color',
  cols: 80,
  rows: 30,
  cwd: process.env.HOME,
  env: process.env
});

ptyProcess.on('data', function(data) {
  console.log(data);
});

ptyProcess.write('ls\r');
```

---

### INDEX (`docs/INDEX.md`)

# VibeTunnel Documentation

## Quick Navigation

### Getting Started
- [Quickstart](guides/quickstart.md) - Installation, first terminal
- [Architecture Overview](core/architecture.md) - System design
- [Native Forwarder](forwarder.md) - PTY, session artifact, and IPC contracts
- [API Reference](core/api-reference.md) - Endpoints, WebSocket protocol

### Development
- [Development Guide](guides/development.md) - Setup, patterns, workflow
- [Testing Guide](guides/testing.md) - Unit, E2E, external devices
- [Deployment Guide](guides/deployment.md) - Production setup

### Platform Guides
- [macOS App](platform/macos.md) - Native app development
- [iOS Companion](platform/ios.md) - Mobile app guide
- [Web Frontend](platform/web.md) - TypeScript/Lit development
- [Linux](linux.md) - Linux dev + npm usage

### Features
- [Authentication](features/authentication.md) - Security, tokens
- [Push Notifications](features/push-notifications.md) - Remote alerts
- [Terminal Features](features/terminal-features.md) - CJK, keyboard

### Reference
- [CLI Tools](reference/cli-tools.md) - vt, claude, gemini commands
- [Troubleshooting](reference/troubleshooting.md) - Common issues
- [Release Process](reference/release-process.md) - Publishing updates

## API Quick Reference

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/sessions` | POST | Create terminal session |
| `/api/sessions` | GET | List active sessions |
| `/api/sessions/:id` | GET | Session details |
| `/api/sessions/:id` | DELETE | Kill session |
| `/ws` | WS | Terminal transport (v3 framing; multiplexed) |
| `/api/sessions/:id/resize` | POST | Resize terminal |
| `/api/auth/token` | POST | Generate auth token |
| `/api/health` | GET | Server health check |

## CLI Commands

| Task | Command | Description |
|------|---------|-------------|
| Start terminal | `vt` | Launch new session |
| View logs | `./scripts/vtlog.sh -n 100` | Last 100 log lines |
| Error logs | `./scripts/vtlog.sh -e` | Errors only |
| Run tests | `pnpm test` | Execute test suite |
| Build Mac | `cd mac && ./scripts/build.sh` | Build release |
| Build iOS | `cd ios && xcodebuild` | Build iOS app |
| Dev server | `cd web && pnpm dev` | Start dev server |

## WebSocket Protocol

WebSocket v3 uses binary frames (`"VT"` magic, version `3`) and multiplexes sessions on a single `/ws` connection.

Details: `docs/websocket.md`.

## Project Structure

```
vibetunnel/
├── mac/           # macOS native app (Swift/SwiftUI)
├── ios/           # iOS companion app (Swift/SwiftUI)
├── web/           # Server & frontend (TypeScript)
│   ├── src/
│   │   ├── server/   # Node.js/Bun server
│   │   └── client/   # Web UI (Lit/TypeScript)
│   └── dist/      # Built artifacts
├── scripts/       # Build & utility scripts
└── docs/          # Documentation
```

## Key Files

| File | Purpose |
|------|---------|
| `mac/VibeTunnel/ServerManager.swift` | Server lifecycle |
| `web/src/server/server.ts` | HTTP/WebSocket server |
| `web/src/server/pty/pty-manager.ts` | Terminal management |
| `web/src/client/app.ts` | Web UI entry point |
| `ios/VibeTunnel/VibeTunnelApp.swift` | iOS app entry |

## Common Tasks

### Add New Feature
1. Check [Architecture](core/architecture.md) for component placement
2. Follow patterns in [Development Guide](guides/development.md)
3. Add tests per [Testing Guide](guides/testing.md)
4. Update API docs if needed

### Debug Issue
1. Check [Troubleshooting](reference/troubleshooting.md)
2. View logs: `./scripts/vtlog.sh -e`
3. Test in dev mode: `pnpm dev`
4. See [Platform Guides](platform/) for specific issues

### Release Update
1. Follow [Release Process](reference/release-process.md)
2. Test on all platforms
3. Update changelog
4. Create GitHub release

## Performance Targets

| Metric | Target | Current |
|--------|--------|---------|
| Startup time | <2s | 1.5s |
| WebSocket latency | <10ms | 5ms |
| Memory usage | <100MB | 80MB |
| CPU idle | <1% | 0.5% |

## Security Model

- **Authentication**: Token-based with optional password
- **Transport**: WSS/HTTPS in production
- **Isolation**: Per-session PTY processes
- **Updates**: Signed & notarized binaries

## Quick Links

- [GitHub Repository](https://github.com/steipete/vibetunnel)
- [API Documentation](core/api-reference.md)
- [Contributing Guide](CONTRIBUTING.md)
- [License](../LICENSE)

---

### Project Overview (`docs/project-overview.md`)

# VibeTunnel Project Overview

VibeTunnel turns any browser into a terminal for your Mac, enabling remote access to command-line tools and AI agents from any device. Built for developers who need to monitor long-running processes, check on AI coding assistants, or share terminal sessions without complex SSH setups.

The project provides a native macOS menu bar application that runs a local HTTP server with WebSocket support for real-time terminal streaming. Users can access their terminals through a responsive web interface at `http://localhost:4020`, with optional secure remote access via Tailscale or ngrok integration.

## Key Files

**Main Entry Points**
- `mac/VibeTunnel/VibeTunnelApp.swift` - macOS app entry point with menu bar integration
- `ios/VibeTunnel/App/VibeTunnelApp.swift` - iOS companion app entry  
- `web/src/cli.ts` - Standalone server CLI entry point (`vibetunnel`)
- `mac/VibeTunnel/Utilities/CLIInstaller.swift` - CLI tool (`vt`) installer

**Core Configuration**
- `web/package.json` - Node.js dependencies and build scripts
- `mac/VibeTunnel.xcodeproj/project.pbxproj` - Xcode project configuration
- `mac/VibeTunnel/version.xcconfig` - Version management
- `apple/Local.xcconfig.template` - Developer configuration template

## Technology Stack

**macOS Application** - Native Swift/SwiftUI app
- Menu bar app: `mac/VibeTunnel/Presentation/Views/MenuBarView.swift`
- Server management: `mac/VibeTunnel/Core/Services/ServerManager.swift` 
- Session monitoring: `mac/VibeTunnel/Core/Services/SessionMonitor.swift`
- Terminal operations: `mac/VibeTunnel/Core/Services/TerminalManager.swift`
- Sparkle framework for auto-updates

**Web Server** - Node.js/TypeScript with Bun runtime
- HTTP/WebSocket server: `web/src/server/server.ts`
- Terminal forwarding: Rust native forwarder `vibetunnel-fwd` (`native/vt-fwd`); see [Native Forwarder](forwarder.md)
- Session management: `web/src/server/lib/sessions.ts`
- PTY integration: `@homebridge/node-pty-prebuilt-multiarch`

**Web Frontend** - Modern TypeScript/Lit web components  
- Terminal rendering: `web/src/client/components/terminal-viewer.ts`
- WebSocket client: `web/src/client/lib/websocket-client.ts`
- UI styling: Tailwind CSS (`web/src/client/styles.css`)
- Build system: esbuild bundler

**iOS Application** - SwiftUI companion app
- Connection management: `ios/VibeTunnel/App/VibeTunnelApp.swift` (lines 40-107)
- Terminal viewer: `ios/VibeTunnel/Views/Terminal/TerminalView.swift`
- WebSocket client: `ios/VibeTunnel/Services/BufferWebSocketClient.swift`

## Platform Support

**macOS Requirements**
- macOS 14.0+ (Sonoma or later)
- Apple Silicon Mac (M1+)
- Xcode 15+ for building from source
- Code signing for proper terminal permissions

**Linux & Headless Support**
- Any Linux distribution with Node.js 22.12 through 24.x
- Runs as standalone server via npm package
- No GUI required - perfect for VPS/cloud deployments
- Install: `npm install vibetunnel`
- Run: `vibetunnel-server`

**iOS Requirements**  
- iOS 17.0+
- iPhone or iPad
- Network access to VibeTunnel server

**Browser Support**
- Modern browsers with WebSocket support
- Mobile-responsive design for phones/tablets
- Terminal rendering via canvas/WebGL

**Server Platforms**
- Primary: Bun runtime (Node.js compatible)
- Build requirements: Node.js 22.12 through 24.x, npm/bun, and rustup for the native forwarder
- Supports macOS, Linux, and headless environments

**Key Platform Files**
- macOS app bundle: `mac/VibeTunnel.xcodeproj`
- iOS app: `ios/VibeTunnel.xcodeproj`  
- Web server: `web/` directory with TypeScript source
- CLI tool: Installed to `/usr/local/bin/vt` (macOS only)
- npm package: `vibetunnel` on npm registry

---

### Apple Docs Index (`apple/docs/index.md`)

# Apple UI Documentation Index

## Quick Navigation

### Liquid Glass Design
- [Overview](liquid-glass/overview.md) - Core concepts, platform availability
- [AppKit Implementation](liquid-glass/appkit.md) - NSGlassEffectView usage
- [SwiftUI Implementation](liquid-glass/swiftui.md) - glassEffect() modifier
- [Common Patterns](liquid-glass/patterns.md) - Reusable components & techniques

### Toolbar Features  
- [SwiftUI Toolbar Features](toolbar/swiftui-features.md) - Customization, search, transitions

## API Quick Lookup

### Liquid Glass APIs

| Task | SwiftUI | AppKit |
|------|---------|--------|
| Apply glass | `.glassEffect()` | `NSGlassEffectView()` |
| Set shape | `.glassEffect(in: .rect(cornerRadius: 16))` | `glass.cornerRadius = 16` |
| Add tint | `.glassEffect(.regular.tint(.blue))` | `glass.tintColor = NSColor.blue` |
| Make interactive | `.glassEffect(.regular.interactive())` | Custom mouse tracking |
| Container | `GlassEffectContainer { }` | `NSGlassEffectContainerView()` |
| Button style | `.buttonStyle(.glass)` | Custom GlassButton class |
| Morphing | `.glassEffectID("id", in: namespace)` | Manual animation |

### Toolbar APIs

| Task | SwiftUI Code |
|------|--------------|
| Customizable toolbar | `.toolbar(id: "main") { }` |
| Add item | `ToolbarItem(id: "save") { }` |
| Add spacer | `ToolbarSpacer(.flexible)` |
| Minimize search | `.searchToolbarBehavior(.minimize)` |
| Reposition system item | `DefaultToolbarItem(kind: .search, placement: .bottomBar)` |
| Transition source | `.matchedTransitionSource(id: "btn", in: namespace)` |

## Decision Trees

### When to Use Liquid Glass

```
Need glass effect?
├─ Single view → .glassEffect() / NSGlassEffectView
└─ Multiple views
   ├─ Need merging → Use Container
   └─ Independent → Individual glass effects
```

### Choosing Framework

```
Platform target?
├─ iOS only → SwiftUI
├─ macOS only
│  ├─ Need AppKit integration → NSGlassEffectView
│  └─ Pure SwiftUI app → .glassEffect()
└─ Cross-platform → SwiftUI with platform checks
```

## Performance Guidelines

| Scenario | Recommendation | Max Count |
|----------|---------------|-----------|
| Static UI | Individual glass effects | 5-10 |
| Dynamic lists | Container + lazy loading | 20-30 |
| Animations | Disable during transitions | N/A |
| Scrolling | Disable glass when scrolling | N/A |

## Common Tasks

### Create Glass Button
- **SwiftUI**: `Button("Click").buttonStyle(.glass)`
- **AppKit**: See [Glass Button Pattern](liquid-glass/patterns.md#glass-button)

### Animate Glass Tint
- **SwiftUI**: `.glassEffect(.regular.tint(condition ? .blue : .clear))`
- **AppKit**: `NSAnimationContext` with `animator().tintColor`

### Merge Glass Effects
- **SwiftUI**: Wrap in `GlassEffectContainer(spacing: 40)`
- **AppKit**: Use `NSGlassEffectContainerView` with spacing

### Custom Toolbar
- **SwiftUI**: `.toolbar(id: "main")` with `ToolbarItem(id: "item")`

## Platform Requirements

| Feature | iOS/iPadOS | macOS | visionOS |
|---------|------------|-------|----------|
| Liquid Glass | 17.0+ | 14.0+ | 1.0+ |
| Customizable Toolbars | 17.0+ | 14.0+ | N/A |
| Glass Button Style | 17.0+ | 14.0+ | 1.0+ |
| Matched Transitions | 17.0+ | 14.0+ | 1.0+ |

## Migration Guide

### AppKit → SwiftUI
```swift
// AppKit
let glass = NSGlassEffectView()
glass.cornerRadius = 16
glass.tintColor = NSColor.blue

// SwiftUI equivalent
View()
    .glassEffect(.regular.tint(.blue), in: .rect(cornerRadius: 16))
```

### Old Toolbar → New Toolbar
```swift
// Old
.toolbar {
    Button("Save") { }
}

// New (customizable)
.toolbar(id: "main") {
    ToolbarItem(id: "save") {
        Button("Save") { }
    }
}
```

## Resources

### Apple Documentation
- [NSGlassEffectView](https://developer.apple.com/documentation/AppKit/NSGlassEffectView)
- [View.glassEffect](https://developer.apple.com/documentation/SwiftUI/View/glassEffect)
- [CustomizableToolbarContent](https://developer.apple.com/documentation/SwiftUI/CustomizableToolbarContent)

### Sample Projects
- [Landmarks: Liquid Glass](https://developer.apple.com/documentation/SwiftUI/Landmarks-Building-an-app-with-Liquid-Glass)

### WWDC Sessions
- Liquid Glass design principles
- Toolbar customization in SwiftUI

---

### Guides Quickstart (`docs/guides/quickstart.md`)

# Quickstart Guide

## Installation

### Download & Install
1. Download VibeTunnel.dmg from [Releases](https://github.com/steipete/vibetunnel/releases)
2. Open DMG and drag VibeTunnel to Applications
3. Launch VibeTunnel from Applications
4. Grant accessibility permissions when prompted

### First Terminal

```bash
# Open a terminal session in your browser
vt

# Named session
vt --name "Project Build"

# Custom command
vt --command "htop"
```

The browser opens automatically at `http://localhost:4020`

## Essential Commands

| Command | Purpose |
|---------|---------|
| `vt` | Start new terminal session |
| `vt list` | Show active sessions |
| `vt kill <id>` | Terminate session |
| `vt logs` | View server logs |
| `vt --help` | Show all options |

## Configuration

### Settings Location
```
~/Library/Preferences/com.steipete.VibeTunnel.plist
```

### Key Settings

| Setting | Default | Options |
|---------|---------|---------|
| Port | 4020 | Any available port |
| Authentication | None | Password, Token |
| Network | Localhost | LAN, Tailscale |
| Auto-start | Disabled | Enable at login |

### Enable LAN Access
1. Click VibeTunnel menu bar icon
2. Select Preferences
3. Toggle "Allow LAN Connections"
4. Set password for security

## Development Mode

### Using Development Server
```bash
# Enable in VibeTunnel settings
Settings → Debug → Use Development Server

# Or run manually
cd web
pnpm install
pnpm dev
```

Benefits:
- Hot reload for web changes
- No Mac app rebuild needed
- Faster iteration

## Common Workflows

### Monitor AI Agents
```bash
# Start Claude Code in VibeTunnel
vt --name "Claude Code"
claude

# Access from another device
http://your-mac-ip:4020
```

### Remote Development
```bash
# With Tailscale
vt --tailscale

# With ngrok
vt --ngrok
```

### Multiple Sessions
```bash
# Start multiple named sessions
vt --name "Frontend" --command "cd ~/frontend && npm run dev"
vt --name "Backend" --command "cd ~/backend && npm start"
vt --name "Database" --command "docker-compose up"
```

## Keyboard Shortcuts

### Terminal

| Shortcut | Action |
|----------|--------|
| `Cmd+C` | Copy selection |
| `Cmd+V` | Paste |
| `Cmd+K` | Clear terminal |
| `Cmd+T` | New session |
| `Cmd+W` | Close session |

### Web Interface

| Shortcut | Action |
|----------|--------|
| `Ctrl+Shift+C` | Copy |
| `Ctrl+Shift+V` | Paste |
| `Alt+1-9` | Switch tabs |
| `Ctrl+Alt+T` | New terminal |

## Troubleshooting Quick Fixes

### Server Won't Start
```bash
# Check if port is in use
lsof -i :4020

# Kill existing process
killall node

# Restart VibeTunnel
osascript -e 'quit app "VibeTunnel"'
open -a VibeTunnel
```

### Can't Connect
```bash
# Check server status
curl http://localhost:4020/api/health

# View logs
./scripts/vtlog.sh -e
```

### Permission Issues
1. System Preferences → Security & Privacy
2. Privacy → Accessibility
3. Add VibeTunnel.app
4. Restart VibeTunnel

## Next Steps

- [Development Setup](development.md) - Build from source
- [API Reference](../core/api-reference.md) - Integrate with VibeTunnel
- [iOS App Setup](../platform/ios.md) - Mobile access
- [Security Guide](../features/authentication.md) - Secure your sessions

## Quick Tips

1. **Auto-start**: Enable "Launch at Login" in preferences
2. **Custom port**: Set `VT_PORT=8080` environment variable
3. **Debug mode**: Hold Option while clicking menu bar icon
4. **Force quit session**: `vt kill --force <id>`
5. **Export recordings**: Sessions saved in `~/.vibetunnel/recordings/`

---

### Apple Docs Liquid Glass Overview (`apple/docs/liquid-glass/overview.md`)

# Liquid Glass Design Overview

## What is Liquid Glass?

Dynamic material design combining optical glass properties with fluidity:
- **Blurs** content behind it
- **Reflects** color and light from surroundings  
- **Reacts** to touch and pointer interactions
- **Morphs** between shapes during transitions

## Platform Availability

| Platform | Framework | Primary Class/Modifier | Min Version |
|----------|-----------|----------------------|-------------|
| macOS | AppKit | `NSGlassEffectView` | macOS 14.0+ |
| macOS | SwiftUI | `.glassEffect()` | macOS 14.0+ |
| iOS/iPadOS | SwiftUI | `.glassEffect()` | iOS 17.0+ |

## Core Concepts

### Glass Variants
- **Regular**: Standard glass effect
- **Prominent**: Enhanced visibility with tint
- **Interactive**: Responds to user input

### Container Optimization
Containers improve performance and enable effect merging:
- **AppKit**: `NSGlassEffectContainerView`
- **SwiftUI**: `GlassEffectContainer`

### Key Properties
- `cornerRadius` / `shape`: Visual appearance
- `tintColor` / `.tint()`: Color overlay
- `spacing`: Merge distance threshold

## Quick Start

### SwiftUI
```swift
Text("Hello").glassEffect()
Button("Click").buttonStyle(.glass)
```

### AppKit
```swift
let glass = NSGlassEffectView()
glass.cornerRadius = 12.0
glass.contentView = myView
```

## Performance Guidelines
1. Use containers for multiple glass views
2. Limit total glass effects on screen
3. Consider GPU impact on older devices
4. Batch similar effects together

## Next Steps
- [AppKit Implementation](appkit.md)
- [SwiftUI Implementation](swiftui.md)
- [Common Patterns](patterns.md)

---

### ARCHITECTURE (`docs/ARCHITECTURE.md`)

# VibeTunnel Architecture

VibeTunnel is a modern terminal multiplexer with native macOS and iOS applications, featuring a Node.js/Bun-powered server backend and real-time web interface. The architecture prioritizes performance, security, and seamless cross-platform experience through WebSocket-based communication and native UI integration.

The system consists of four main components: a native macOS menu bar application that manages server lifecycle, a Node.js/Bun server handling terminal sessions, an iOS companion app for mobile terminal access, and a web frontend for browser-based interaction. These components communicate through a well-defined REST API and WebSocket protocol for real-time terminal I/O streaming.

## Component Map

**macOS Application** - Native Swift app in mac/VibeTunnel/
- ServerManager (mac/VibeTunnel/Core/Services/ServerManager.swift) - Central server lifecycle coordinator
- BunServer (mac/VibeTunnel/Core/Services/BunServer.swift) - Bun runtime integration  
- BaseProcessServer (mac/VibeTunnel/Core/Services/BaseProcessServer.swift) - Base class for server implementations
- TTYForwardManager (mac/VibeTunnel/Core/Services/TTYForwardManager.swift) - Terminal forwarding logic
- SessionMonitor (mac/VibeTunnel/Core/Services/SessionMonitor.swift) - Active session tracking

**Node.js/Bun Server** - JavaScript backend in web/src/server/
- app.ts - Express application setup and configuration
- server.ts - HTTP server initialization and shutdown handling
- pty/pty-manager.ts - Native PTY process management
- pty/session-manager.ts - Terminal session lifecycle
- services/terminal-manager.ts - High-level terminal operations
- services/ws-v3-hub.ts - Unified `/ws` WebSocket v3 hub (multiplexed)
- routes/sessions.ts - REST API endpoints for session management

**iOS Application** - Native iOS app in ios/VibeTunnel/
- BufferWebSocketClient (ios/VibeTunnel/Services/BufferWebSocketClient.swift) - WebSocket client for terminal streaming
- TerminalView (ios/VibeTunnel/Views/Terminal/TerminalView.swift) - Terminal rendering UI
- GhosttyWebView (ios/VibeTunnel/Views/Terminal/GhosttyWebView.swift) - WKWebView-based terminal renderer

**Web Frontend** - TypeScript/React app in web/src/client/
- Terminal rendering using ghostty-web
- WebSocket client for real-time updates
- Session management UI

## Key Files

**Server Protocol Definition**
- mac/VibeTunnel/Core/Protocols/VibeTunnelServer.swift - Defines server interface

**Session Models**
- mac/VibeTunnel/Core/Models/TunnelSession.swift - Core session data structure
- web/src/server/pty/types.ts - TypeScript session types

**Binary Integration**
- mac/scripts/build-bun-executable.sh - Builds Bun runtime bundle
- web/build-native.js - Native module compilation for pty.node

**Configuration**
- mac/VibeTunnel/Core/Models/AppConstants.swift - Application constants
- web/src/server/app.ts (lines 20-31) - Server configuration interface

## Data Flow

**Session Creation Flow**
1. Client request → POST /api/sessions (web/src/server/routes/sessions.ts:createSessionRoutes)
2. TerminalManager.createTerminal() (web/src/server/services/terminal-manager.ts) 
3. PtyManager.spawn() (web/src/server/pty/pty-manager.ts) - Spawns native PTY process
4. Session stored in manager, WebSocket upgrade prepared
5. Response with `sessionId` (client connects to `/ws` and subscribes)

**Terminal I/O Stream**
1. Client connects → WebSocket upgrade to `/ws` (v3 framing)
2. Client subscribes → `SUBSCRIBE(sessionId, flags)` frames
3. Client input/resize → `INPUT_*` / `RESIZE` frames
4. Server routes frames → `WsV3Hub` (web/src/server/services/ws-v3-hub.ts)
5. PTY output → cast tail (`CastOutputHub`) → `STDOUT` frames
6. Server-side Ghostty → `SNAPSHOT_VT` frames for previews/resync
7. Client renders:
   - interactive: `ghostty-web` (`vibe-terminal`)
   - previews: VT snapshots (`vibe-terminal-buffer`)

**Buffer Optimization Protocol**
- WS v3 framing uses `VT` magic + version `3` (see docs/websocket.md)
- Full VT snapshots (`SNAPSHOT_VT`) for previews/resync
- Stdout streaming (`STDOUT`) for interactive terminals

**Server Lifecycle Management**
1. ServerManager.start() (mac/VibeTunnel/Core/Services/ServerManager.swift)
2. Creates BunServer instance
3. BaseProcessServer.start() spawns server process
4. Health checks via HTTP /health endpoint
5. Log streaming through Process.standardOutput pipe
6. Graceful shutdown on stop() with SIGTERM

**Remote Access Architecture**
- NgrokService (mac/VibeTunnel/Core/Services/NgrokService.swift) - Secure tunnel creation
- HQClient (web/src/server/services/hq-client.ts) - Headquarters mode for multi-server
- RemoteRegistry (web/src/server/services/remote-registry.ts) - Remote server discovery

**Authentication Flow**
- Basic Auth middleware (web/src/server/middleware/auth.ts)
- Credentials stored in macOS Keychain via DashboardKeychain service
- Optional password protection for network access

---

### CONTRIBUTING (`docs/CONTRIBUTING.md`)

# Contributing to VibeTunnel

We love your input! We want to make contributing to VibeTunnel as easy and transparent as possible, whether it's:

- Reporting a bug
- Discussing the current state of the code
- Submitting a fix
- Proposing new features
- Becoming a maintainer

## Development Setup

### Prerequisites

1. **macOS 14.0+** (Sonoma or later)
2. **Xcode 16.0+** with Swift 6.0 support
3. **Node.js 22.12 through 24.x**: `brew install node@24`
4. **Bun runtime**: `curl -fsSL https://bun.sh/install | bash`
5. **Rustup**: The native forwarder toolchain is pinned by `native/vt-fwd/rust-toolchain.toml`
6. **Git**: For version control

### Getting Started

1. **Fork and clone the repository**
   ```bash
   git clone https://github.com/[your-username]/vibetunnel.git
   cd vibetunnel
   ```

2. **Set up development environment**
   ```bash
   # Install Node.js dependencies
   cd web
   pnpm install

   # Start the development server (keep this running)
   pnpm run dev
   ```

3. **Open the Xcode project**
   ```bash
   # From the root directory
   open mac/VibeTunnel-Mac.xcodeproj
   ```

4. **Configure code signing (optional for development)**
   - Copy `apple/Local.xcconfig.template` to `apple/Local.xcconfig`
   - Add your development team ID (or leave empty for ad-hoc signing)
   - This file is gitignored to keep your settings private

## Development Workflow

### Working with the Web Server

The web server (Node.js/TypeScript) runs in development mode with hot reloading:

```bash
cd web
pnpm run dev  # Keep this running in a separate terminal
```

**Custom Port Configuration** (if port 4020 is already in use):
```bash
# Option 1: Run server directly with custom port (cleanest approach)
pnpm run dev:server --port 4021

# Option 2: Using environment variable
PORT=4021 pnpm run dev

# Option 3: Using the full dev command with arguments (requires --)
pnpm run dev -- --port 4021
```

**Development Commands**:
- `pnpm run dev` - Run everything (server, client watcher, CSS, assets)
- `pnpm run dev:server` - Run just the server (accepts --port directly!)
- `pnpm run dev:client` - Run just client-side watchers

**Important**: Never manually build the web project - the development server handles all compilation automatically.

### Working with the macOS App

1. Open `mac/VibeTunnel.xcworkspace` in Xcode
2. Select the VibeTunnel scheme
3. Build and run (⌘R)

The app will automatically use the development server running on `http://localhost:4020`.

### Working with the iOS App

1. Open `ios/VibeTunnel.xcodeproj` in Xcode
2. Select your target device/simulator
3. Build and run (⌘R)

## Code Style Guidelines

### Swift Code

We use modern Swift 6.0 patterns with strict concurrency checking:

- **SwiftFormat**: Automated formatting with `.swiftformat` configuration
- **SwiftLint**: Linting rules in `.swiftlint.yml`
- Use `@MainActor` for UI-related code
- Use `@Observable` for SwiftUI state objects
- Prefer `async/await` over completion handlers

Run before committing:
```bash
cd mac
swiftformat .
swiftlint
```

### TypeScript/JavaScript Code

- **Biome**: For code formatting and linting (replaces ESLint + Prettier)
- **TypeScript**: Strict mode enabled

Run before committing:
```bash
cd web
pnpm run precommit   # Runs format + lint fixes + typecheck in one command
```

Or run individually if needed:
```bash
pnpm run format      # Format with Biome
pnpm run lint        # Check with Biome + TypeScript
pnpm run lint:fix    # Auto-fix Biome issues
pnpm run typecheck   # Check TypeScript types only
```

### Important Rules

- **NEVER use `setTimeout` in frontend code** unless explicitly necessary
- **Always fix ALL lint and type errors** before committing
- **Never commit without user testing** the changes
- **No hardcoded values** - use configuration files
- **No console.log in production code** - use proper logging

## Project Structure

```
vibetunnel/
├── mac/                    # macOS application
│   ├── VibeTunnel/        # Swift source code
│   │   ├── Core/          # Business logic
│   │   ├── Presentation/  # UI components
│   │   └── Utilities/     # Helper functions
│   ├── VibeTunnelTests/   # Unit tests
│   └── scripts/           # Build and release scripts
│
├── ios/                   # iOS companion app
│   └── VibeTunnel/        # Swift source code
│
├── native/vt-fwd/         # Rust terminal forwarder
│
├── web/                   # Web server and frontend
│   ├── src/
│   │   ├── server/        # Node.js server (TypeScript)
│   │   └── client/        # Web frontend (Lit/TypeScript)
│   └── public/            # Static assets
│
└── docs/                  # Documentation
```

## Testing

### macOS Tests

We use Swift Testing framework:

```bash
# Run tests in Xcode
xcodebuild test -workspace mac/VibeTunnel.xcworkspace -scheme VibeTunnel

# Or use Xcode UI (⌘U)
```

Test categories (tags):
- `.critical` - Must-pass tests
- `.networking` - Network-related tests
- `.concurrency` - Async operations
- `.security` - Security features

### Web Tests

We use Vitest for Node.js testing:

```bash
cd web
pnpm run test
```

### Writing Tests

- Write tests for all new features
- Include both positive and negative test cases
- Mock external dependencies
- Keep tests focused and fast

## Making a Pull Request

1. **Create a feature branch**
   ```bash
   git checkout -b feature/your-feature-name
   ```

2. **Make your changes**
   - Follow the code style guidelines
   - Write/update tests
   - Update documentation if needed

3. **Test your changes**
   - Run the test suite
   - Test manually in the app
   - Check both macOS and web components

4. **Commit your changes**
   ```bash
   # Web changes
   cd web && pnpm run precommit

   # Swift changes
   cd mac && swiftformat . && swiftlint

   # Commit
   git add .
   git commit -m "feat: add amazing feature"
   ```

5. **Push and create PR**
   ```bash
   git push origin feature/your-feature-name
   ```
   Then create a pull request on GitHub.

## Commit Message Convention

We follow conventional commits:

- `feat:` New feature
- `fix:` Bug fix
- `docs:` Documentation changes
- `style:` Code style changes (formatting, etc)
- `refactor:` Code refactoring
- `test:` Test changes
- `chore:` Build process or auxiliary tool changes

## Debugging Tips

### macOS App
- Use Xcode's debugger (breakpoints, LLDB)
- Check Console.app for system logs
- Enable debug logging in Settings → Debug

### Web Server
- Use Chrome DevTools for frontend debugging
- Server logs appear in the terminal running `pnpm run dev`
- Use `--inspect` flag for Node.js debugging

### Common Issues

**"Port already in use"**
- Another instance might be running (e.g., production VibeTunnel app)
- Check Activity Monitor for `vibetunnel` processes
- Quick fix: `pnpm run dev:server --port 4021` (no -- needed!)
- Or use environment variable: `PORT=4021 pnpm run dev`
- See "Custom Port Configuration" section above for all options

**"Binary not found"**
- Run `cd web && node build-native.js` to build the Bun executable
- Check that `web/native/vibetunnel` exists

**WebSocket connection failures**
- Ensure the server is running (`pnpm run dev`)
- Check for CORS issues in browser console
- Verify the port matches between client and server

## Documentation

When adding new features:

1. Update the relevant documentation in `docs/`
2. Add JSDoc/Swift documentation comments
3. Update README.md if it's a user-facing feature
4. Include examples in your documentation

## Security Considerations

- Never commit secrets or API keys
- Use Keychain for sensitive data storage
- Validate all user inputs
- Follow principle of least privilege
- Test authentication and authorization thoroughly

## Getting Help

- Join our [Discord server](https://discord.gg/vibetunnel) (if available)
- Check existing issues on GitHub
- Read the [Technical Specification](spec.md)
- Ask questions in pull requests

## Code Review Process

All submissions require review before merging:

1. Automated checks must pass (linting, tests)
2. At least one maintainer approval required
3. Resolve all review comments
4. Keep PRs focused and reasonably sized

## License

By contributing, you agree that your contributions will be licensed under the MIT License. See [LICENSE](../LICENSE) for details.

## Thank You!

Your contributions make VibeTunnel better for everyone. We appreciate your time and effort in improving the project! 🎉

---

### INDEX Old (`docs/INDEX-old.md`)

# VibeTunnel Documentation Index

This index provides a comprehensive overview of all documentation in the VibeTunnel project, organized by category and purpose.

## 📚 Main Documentation

### Getting Started
- [**README.md**](../README.md) - Project overview, quick start guide, and basic usage
- [**introduction.mdx**](introduction.mdx) - Mintlify documentation landing page
- [**docs.json**](../docs.json) - Mintlify documentation configuration

### Architecture & Design
- [**ARCHITECTURE.md**](ARCHITECTURE.md) - System architecture, component relationships, data flow
- [**architecture-mario.md**](architecture-mario.md) - Alternative architecture documentation
- [**spec.md**](spec.md) - Core technical specifications and protocols
- [**ios-spec.md**](ios-spec.md) - iOS companion app specification

### Development Guides
- [**CONTRIBUTING.md**](CONTRIBUTING.md) - Contributing guidelines and development workflow
- [**development.md**](development.md) - Development setup, code style, patterns
- [**build-system.md**](build-system.md) - Build system overview and usage
- [**deployment.md**](deployment.md) - Deployment and distribution guide
- [**RELEASE.md**](RELEASE.md) - Comprehensive release process documentation

### Feature Documentation
- [**authentication.md**](authentication.md) - Authentication system and security
- [**push-notification.md**](push-notification.md) - Push notification implementation
- [**security.md**](security.md) - Security configuration and best practices
- [**keyboard-shortcuts.md**](keyboard-shortcuts.md) - Keyboard shortcut reference

### Testing
- [**testing.md**](testing.md) - Testing strategy and test suite documentation
- [**TESTING_EXTERNAL_DEVICES.md**](TESTING_EXTERNAL_DEVICES.md) - Testing on external devices (iPad, etc.)

### Tools & Utilities
- [**claude.md**](claude.md) - Claude CLI usage guide
- [**gemini.md**](gemini.md) - Gemini CLI for large codebase analysis
- [**custom-node.md**](custom-node.md) - Custom Node.js build documentation

### Reference
- [**project-overview.md**](project-overview.md) - High-level project overview
- [**files.md**](files.md) - File catalog and organization
- [**logging-style-guide.md**](logging-style-guide.md) - Logging conventions and style guide
- [**CHANGELOG.md**](../CHANGELOG.md) - Project changelog

## 🍎 Platform-Specific Documentation

### macOS (`mac/`)
- [**mac/README.md**](../mac/README.md) - macOS app overview and quick start
- [**mac/docs/code-signing.md**](../mac/docs/code-signing.md) - Comprehensive code signing guide
- [**mac/docs/BuildArchitectures.md**](../mac/docs/BuildArchitectures.md) - Build architecture details
- [**mac/docs/BuildRequirements.md**](../mac/docs/BuildRequirements.md) - Build requirements
- [**mac/docs/sparkle-keys.md**](../mac/docs/sparkle-keys.md) - Sparkle update framework keys
- [**mac/docs/sparkle-stats-store.md**](../mac/docs/sparkle-stats-store.md) - Update statistics

### iOS (`ios/`)
- [**ios/README.md**](../ios/README.md) - iOS app overview
- [**ios/CLAUDE.md**](../ios/CLAUDE.md) - iOS development guidelines for Claude

### Web (`web/`)
- [**web/README.md**](../web/README.md) - Web server and frontend overview
- [**web/docs/spec.md**](../web/docs/spec.md) - Web server implementation specification
- [**web/docs/performance.md**](../web/docs/performance.md) - Performance optimization guide
- [**web/docs/playwright-testing.md**](../web/docs/playwright-testing.md) - Playwright E2E testing
- [**web/docs/socket-protocol.md**](../web/docs/socket-protocol.md) - WebSocket protocol documentation
- [**web/docs/terminal-titles.md**](../web/docs/terminal-titles.md) - Terminal title management
- [**web/docs/VT_INSTALLATION.md**](../web/docs/VT_INSTALLATION.md) - VT command installation
- [**web/docs/npm.md**](../web/docs/npm.md) - NPM package documentation

### Apple Shared (`apple/`)
- [**apple/docs/modern-swift.md**](../apple/docs/modern-swift.md) - Modern Swift patterns
- [**apple/docs/swift-concurrency.md**](../apple/docs/swift-concurrency.md) - Swift concurrency guide
- [**apple/docs/swift-testing-playbook.md**](../apple/docs/swift-testing-playbook.md) - Swift testing best practices
- [**apple/docs/swiftui.md**](../apple/docs/swiftui.md) - SwiftUI guidelines
- [**apple/docs/logging-private-fix.md**](../apple/docs/logging-private-fix.md) - Logging configuration

## 🤖 AI Assistant Guidelines

### CLAUDE.md Files
These files provide specific instructions for Claude AI when working with different parts of the codebase:

- [**CLAUDE.md**](../CLAUDE.md) - Main project guidelines for Claude
- [**web/CLAUDE.md**](../web/CLAUDE.md) - Web development specific instructions
- [**mac/CLAUDE.md**](../mac/CLAUDE.md) - macOS development guidelines
- [**ios/CLAUDE.md**](../ios/CLAUDE.md) - iOS development guidelines

### GEMINI.md
- [**GEMINI.md**](../GEMINI.md) - Instructions for Gemini AI assistant

## 📋 Documentation Standards

When adding new documentation:

1. **Location**: Place documentation in the most relevant directory
   - General docs in `/docs`
   - Platform-specific docs in their respective directories
   - Keep related documentation together

2. **Naming**: Use clear, descriptive names
   - UPPERCASE.md for important documents (README, CHANGELOG, etc.)
   - lowercase-with-hyphens.md for regular documentation
   - Include platform prefix when needed (ios-spec.md)

3. **Content**: Follow consistent structure
   - Start with a clear title and overview
   - Include practical examples
   - Add cross-references to related docs
   - Keep content up-to-date with code changes

4. **Maintenance**: Regular reviews
   - Remove outdated documentation
   - Update when features change
   - Consolidate duplicate content
   - Maintain this index when adding/removing docs

---

### RELEASE (`docs/RELEASE.md`)

# VibeTunnel Release Documentation

This guide provides comprehensive documentation for creating and publishing releases for VibeTunnel, a macOS menu bar application using Sparkle 2.x for automatic updates.

## ✅ Standard Release Flow (RepoBar parity)
1) **Version + changelog**
   - Update `VibeTunnel/version.xcconfig` (`MARKETING_VERSION`, `CURRENT_PROJECT_VERSION`).
   - Sync `../web/package.json` version.
   - Finalize the top section in `CHANGELOG.md` (no “Unreleased”).

2) **Run the full release script**
   - `./scripts/release.sh beta <n>` or `./scripts/release.sh stable`
   - Generates appcast entries with HTML notes from `CHANGELOG.md`.
   - Release notes helper: `./mac/scripts/generate-release-notes.sh <version> > RELEASE_NOTES.md`

3) **Sparkle UX verification**
   - About → “Check for Updates…”
   - Menu only shows “Update ready, restart now?” once the update is downloaded.
   - Sparkle dialog shows formatted release notes (not escaped HTML).

## 🚀 Quick Release Commands

### Standard Release Flow
```bash
# 1. Update versions
vim VibeTunnel/version.xcconfig  # Set MARKETING_VERSION and increment CURRENT_PROJECT_VERSION
vim ../web/package.json          # Match version with MARKETING_VERSION

# 2. Update changelog
vim CHANGELOG.md                 # Add entry for new version

# 3. Run release
export SPARKLE_ACCOUNT="VibeTunnel"
./scripts/release.sh beta 5      # For beta.5
./scripts/release.sh stable      # For stable release

# If interrupted, resume with:
./scripts/release.sh --resume

# Check release status:
./scripts/release.sh --status
```

### If Release Script Fails

#### After Notarization Success
```bash
# 1. Create DMG (if missing)
./scripts/create-dmg.sh build/Build/Products/Release/VibeTunnel.app

# 2. Create GitHub release
gh release create "v1.0.0-beta.5" \
  --title "VibeTunnel 1.0.0-beta.5" \
  --prerelease \
  --notes-file RELEASE_NOTES.md \
  build/VibeTunnel-*.dmg \
  build/VibeTunnel-*.zip

# 3. Get Sparkle signature (ALWAYS use -f flag!)
sign_update -f private/sparkle_private_key build/VibeTunnel-*.dmg --account VibeTunnel

# 4. Update appcast manually (add to appcast-prerelease.xml)
# 5. Commit and push
git add ../appcast-prerelease.xml
git commit -m "Update appcast for v1.0.0-beta.5"
git push
```

## 🎯 Release Process Overview

VibeTunnel uses an automated release process that handles all the complexity of:
- Building the supported Apple Silicon (arm64) application and embedded server resources
- Code signing and notarization with Apple
- Creating DMG and ZIP files
- Publishing to GitHub
- Updating Sparkle appcast files with EdDSA signatures

## ⚠️ Version Management Best Practices

### Critical Version Rules

1. **Version Configuration Source of Truth**
   - ALL version information is stored in `VibeTunnel/version.xcconfig`
   - The Xcode project must reference these values using `$(MARKETING_VERSION)` and `$(CURRENT_PROJECT_VERSION)`
   - NEVER hardcode versions in the Xcode project

2. **Pre-release Version Suffixes**
   - For pre-releases, the suffix MUST be in version.xcconfig BEFORE running release.sh
   - Example: To release beta 2, set `MARKETING_VERSION = 1.0.0-beta.2` in version.xcconfig
   - The release script will NOT add suffixes - it uses the version exactly as configured

3. **Build Number Management**
   - Build numbers MUST be incremented for EVERY release (including pre-releases)
   - Build numbers MUST be monotonically increasing
   - Sparkle uses build numbers, not version strings, to determine if an update is available

### Common Version Management Mistakes

❌ **MISTAKE**: Running `./scripts/release.sh beta 2` when version.xcconfig already has `1.0.0-beta.2`
- **Result**: Creates version `1.0.0-beta.2-beta.2` (double suffix)
- **Fix**: The release type and number are only for tagging, not version modification

❌ **MISTAKE**: Forgetting to increment build number
- **Result**: Sparkle won't detect the update even with a new version
- **Fix**: Always increment CURRENT_PROJECT_VERSION in version.xcconfig

❌ **MISTAKE**: Hardcoding versions in Xcode project instead of using version.xcconfig
- **Result**: Version mismatches between built app and expected version
- **Fix**: Ensure Xcode project uses `$(MARKETING_VERSION)` and `$(CURRENT_PROJECT_VERSION)`

### Version Workflow Example

For releasing 1.0.0-beta.2:

1. **Edit version.xcconfig**:
   ```
   MARKETING_VERSION = 1.0.0-beta.2  # Add suffix here
   CURRENT_PROJECT_VERSION = 105      # Increment from previous build
   ```

2. **Verify configuration**:
   ```bash
   ./scripts/preflight-check.sh
   # This will warn if version already has a suffix
   ```

3. **Run release**:
   ```bash
   ./scripts/release.sh beta 2
   # The "beta 2" parameters are ONLY for git tagging
   ```

## 📋 Pre-Release Checklist

**Automated Checklist**: Run `./scripts/release-checklist.sh` for an interactive pre-release validation.

Before running ANY release commands, verify these items:

### ⚠️ CRITICAL: Sparkle Signature Verification
- [ ] **Verify private key exists at `private/sparkle_private_key`**
- [ ] **Confirm you will use the `-f` flag with ALL sign_update commands**
- [ ] **Test sign a dummy file to ensure correct key:**
  ```bash
  echo "test" > test.txt
  sign_update -f private/sparkle_private_key test.txt
  # Should produce a signature starting with a valid EdDSA signature
  rm test.txt
  ```
- [ ] **NEVER use sign_update without the `-f` flag!**
- [ ] **The public key in Info.plist is: `AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=`**
- [ ] **Run signature validation script:**
  ```bash
  ./scripts/validate-sparkle-signature.sh
  # Should show all signatures are valid
  ```

### Environment Setup
- [ ] Ensure stable internet connection (notarization requires consistent connectivity)
- [ ] Check Apple Developer status page for any service issues
- [ ] Have at least 30 minutes available (full release takes 15-20 minutes)
- [ ] Close other resource-intensive applications
- [ ] Ensure you're on main branch
  ```bash
  git checkout main
  git pull --rebase origin main
  git status  # Check for uncommitted changes
  ```

### Version Verification
- [ ] **⚠️ CRITICAL: Version in version.xcconfig is EXACTLY what you want to release**
  ```bash
  grep MARKETING_VERSION VibeTunnel/version.xcconfig
  # For beta.2 should show: MARKETING_VERSION = 1.0.0-beta.2
  # NOT: MARKETING_VERSION = 1.0.0
  ```
  ⚠️ **WARNING**: The release script uses this version AS-IS. It will NOT add suffixes!
  
- [ ] **Build number is incremented**
  ```bash
  grep CURRENT_PROJECT_VERSION VibeTunnel/version.xcconfig
  # Must be higher than the last release
  ```
  
- [ ] **Web package.json version matches macOS version**
  ```bash
  # Check web version matches macOS version
  grep '"version"' ../web/package.json
  # Should match MARKETING_VERSION from version.xcconfig
  ```
  ⚠️ **IMPORTANT**: The web frontend version must be synchronized with the macOS app version!
  
- [ ] **CHANGELOG.md has entry for this version**
  ```bash
  grep "## \[1.0.0-beta.2\]" CHANGELOG.md
  # Must exist with release notes
  ```

### Environment Variables
- [ ] Set required environment variables:
  ```bash
  export SPARKLE_ACCOUNT="VibeTunnel"
  export APP_STORE_CONNECT_KEY_ID="YOUR_KEY_ID"
  export APP_STORE_CONNECT_ISSUER_ID="YOUR_ISSUER_ID"
  export APP_STORE_CONNECT_API_KEY_P8="-----BEGIN PRIVATE KEY-----
  YOUR_PRIVATE_KEY_CONTENT
  -----END PRIVATE KEY-----"
  ```

### Clean Build
- [ ] Clean build and derived data if needed:
  ```bash
  ./scripts/clean.sh
  rm -rf build DerivedData
  rm -rf ~/Library/Developer/Xcode/DerivedData/VibeTunnel-*
  ```

### File Verification
- [ ] CHANGELOG.md exists and has entry for new version
- [ ] Sparkle private key exists at expected location
- [ ] No stuck DMG volumes in /Volumes/
  ```bash
  # Check for stuck volumes
  ls /Volumes/VibeTunnel*
  # Unmount if needed
  for volume in /Volumes/VibeTunnel*; do
      hdiutil detach "$volume" -force
  done
  ```
- [ ] **Check for unexpected files in the app bundle**
  ```bash
  # Check for node_modules or other development files
  find build/Build/Products/Release/VibeTunnel.app -name "node_modules" -type d
  find build/Build/Products/Release/VibeTunnel.app -name "*.jar" -type f
  # Should return empty - no development files in release build
  ```

## 🚀 Creating a Release

### Step 1: Pre-flight Check
```bash
# Run the comprehensive release checklist
./scripts/release-checklist.sh

# Then run the automated preflight check
./scripts/preflight-check.sh
```
These scripts validate your environment is ready for release.

### Step 2: CRITICAL Pre-Release Version Check
**IMPORTANT**: Before running the release script, ensure your version.xcconfig is set correctly:

1. For beta releases: The MARKETING_VERSION should already include the suffix (e.g., `1.0.0-beta.2`)
2. The release script will NOT add additional suffixes - it uses the version as-is
3. Always verify the version before proceeding:
   ```bash
   grep MARKETING_VERSION VibeTunnel/version.xcconfig
   # Should show: MARKETING_VERSION = 1.0.0-beta.2
   ```

**Common Mistake**: If the version is already `1.0.0-beta.2` and you run `./scripts/release.sh beta 2`, 
it will create `1.0.0-beta.2-beta.2` which is wrong!

### Step 3: Create/Update CHANGELOG.md
Before creating any release, ensure the CHANGELOG.md file exists in the project root (`/vibetunnel/CHANGELOG.md`) and contains a proper section for the version being released:

```markdown
# Changelog

All notable changes to VibeTunnel will be documented in this file.

## [1.0.0-beta.2] - 2025-06-19

### 🎨 UI Improvements
- **Enhanced feature** - Description of the improvement
...
```

**CRITICAL**: The release process uses the CHANGELOG.md file in the project root as the single source of truth for release notes. The changelog must be updated with the new version section BEFORE running the release script.

**Key Points**:
- **Location**: CHANGELOG.md must be at `/vibetunnel/CHANGELOG.md` (project root, NOT in `mac/`)
- **No RELEASE_NOTES.md files**: The release process does NOT use RELEASE_NOTES.md files
- **Per-Version Extraction**: The release script automatically extracts ONLY the changelog section for the specific version being released
- **GitHub Release**: Uses the extracted markdown content directly (via `generate-release-notes.sh`)
- **Sparkle Appcast**: Converts the markdown to HTML for update dialogs

The release script uses these helper scripts:
- `generate-release-notes.sh` - Extracts markdown release notes for GitHub
- `changelog-to-html.sh` - Converts markdown to HTML for Sparkle appcast
- `find-changelog.sh` - Reliably locates CHANGELOG.md from any directory

### Step 4: Create the Release

⚠️ **CRITICAL UNDERSTANDING**: The release script parameters are ONLY for:
1. Git tag creation
2. Determining if it's a pre-release on GitHub
3. Validation that your version.xcconfig matches your intent

The script will NEVER modify the version - it uses version.xcconfig exactly as configured!

For long-running operations, consider using screen or tmux:
```bash
# Run in a screen/tmux session to prevent disconnection
screen -S release
./scripts/release.sh beta 5 --verbose --log
```

**IMPORTANT**: When using Claude Code or any automated tool, NEVER run the release script in the background. Always run it directly in the foreground to ensure proper completion and error handling.

```bash
# For stable releases:
./scripts/release.sh stable

# For pre-releases:
./scripts/release.sh beta 2
# The "beta 2" parameters are ONLY for git tagging
```

**Script Validation**: The release script now includes:
- Double-suffix detection (prevents 1.0.0-beta.2-beta.2)
- Build number uniqueness check
- Version consistency verification
- Notarization credential validation

**IMPORTANT**: The release script does NOT automatically increment build numbers. You must manually update the build number in VibeTunnel.xcodeproj before running the script, or it will fail the pre-flight check.

The script will:
1. Validate build number is unique and incrementing
2. Build, sign, and notarize the app
3. Create a DMG
4. Publish to GitHub
5. Update the appcast files with EdDSA signatures
6. Commit and push all changes

**Note**: Notarization can take 5-10 minutes depending on Apple's servers. This is normal.

### Step 5: Verify Success
- Check the GitHub releases page
- **IMPORTANT**: Verify the GitHub release shows ONLY the current version's changelog, not the entire history
  - If it shows the full changelog, the release notes were not generated correctly
  - The release should only show changes for that specific version (e.g., beta.10 shows only beta.10 changes)
- **Monitor app size**: Verify the DMG size is reasonable (expected: ~42-44 MB)
  ```bash
  # Check DMG size
  ls -lh build/VibeTunnel-*.dmg
  # Compare with previous releases
  gh release list --limit 5 | grep -E "beta|stable"
  # Download and check sizes
  for tag in $(gh release list --limit 5 | awk '{print $3}'); do
      echo "=== $tag ==="
      gh release view "$tag" --json assets --jq '.assets[] | "\(.name): \(.size) bytes (\(.size/1024/1024 | floor) MB)"'
  done
  ```
  - If size increased significantly (>5MB), investigate for bundled development files
- Verify the appcast was updated correctly with proper changelog content
- **Critical**: Verify the Sparkle signature is correct:
  ```bash
  # Download and verify the DMG signature
  curl -L -o test.dmg <github-dmg-url>
  sign_update -f private/sparkle_private_key test.dmg --account VibeTunnel
  # Compare with appcast sparkle:edSignature
  ```
- Test updating from a previous version
- **Important**: Verify that the Sparkle update dialog shows the formatted changelog, not HTML tags
- **CRITICAL**: Check that update installs without "improperly signed" errors
  - If you get "improperly signed" error, the appcast has wrong signature
  - Regenerate with: `sign_update -f private/sparkle_private_key [dmg-file]`
  - Update appcast XML with correct signature
  - Run `./scripts/validate-sparkle-signature.sh` to verify all signatures
- Verify Stats.store is serving the updated appcast (1-minute cache):
  ```bash
  curl -H "User-Agent: VibeTunnel/X.X.X Sparkle/2.7.1" \
       https://stats.store/api/v1/appcast/appcast-prerelease.xml | \
       grep sparkle:edSignature
  ```

### If Interrupted

If the release script is interrupted:
```bash
./scripts/check-release-status.sh 1.0.0-beta.5
./scripts/release.sh --resume
```

## 🛠️ Manual Process (If Needed)

If the automated script fails, here's the manual process:

### 1. Update Version Numbers
Edit version configuration files:

**macOS App** (`VibeTunnel/version.xcconfig`):
- Update MARKETING_VERSION
- Update CURRENT_PROJECT_VERSION (build number)

**Web Frontend** (`../web/package.json`):
- Update "version" field to match MARKETING_VERSION

**Note**: The Xcode project file is named `VibeTunnel-Mac.xcodeproj`

### 2. Clean and Build Universal Binary
```bash
rm -rf build DerivedData
./scripts/build.sh --configuration Release
```

### 3. Sign and Notarize
```bash
./scripts/sign-and-notarize.sh build/Build/Products/Release/VibeTunnel.app
```

### 4. Create DMG and ZIP
```bash
./scripts/create-dmg.sh build/Build/Products/Release/VibeTunnel.app
./scripts/create-zip.sh build/Build/Products/Release/VibeTunnel.app
```

### 5. Sign DMG for Sparkle
```bash
export PATH="$HOME/.local/bin:$PATH"
# CRITICAL: Always use -f flag with private key file!
sign_update -f private/sparkle_private_key build/VibeTunnel-X.X.X.dmg
```

### 6. Create GitHub Release
```bash
gh release create "v1.0.0-beta.1" \
  --title "VibeTunnel 1.0.0-beta.1" \
  --notes "Beta release 1" \
  --prerelease \
  build/VibeTunnel-*.dmg \
  build/VibeTunnel-*.zip
```

### 7. Update Appcast
```bash
./scripts/update-appcast.sh
git add appcast*.xml
git commit -m "Update appcast for v1.0.0-beta.1"
git push
```

## 🔍 Verification Commands

```bash
# Check release artifacts
ls -la build/VibeTunnel-*.dmg
ls -la build/VibeTunnel-*.zip

# Check GitHub release
gh release view v1.0.0-beta.5

# Verify Sparkle signature (ALWAYS use -f flag!)
curl -L -o test.dmg [github-dmg-url]
sign_update -f private/sparkle_private_key test.dmg --account VibeTunnel

# Check appcast
grep "1.0.0-beta.5" ../appcast-prerelease.xml

# Verify app in DMG
hdiutil attach test.dmg
spctl -a -vv /Volumes/VibeTunnel/VibeTunnel.app
hdiutil detach /Volumes/VibeTunnel
```

## ⚠️ Critical Requirements

### 1. Build Numbers MUST Increment
Sparkle uses build numbers (CFBundleVersion) to determine updates, NOT version strings!

| Version | Build | Result |
|---------|-------|--------|
| 1.0.0-beta.1 | 100 | ✅ |
| 1.0.0-beta.2 | 101 | ✅ |
| 1.0.0-beta.3 | 99  | ❌ Build went backwards |
| 1.0.0 | 101 | ❌ Duplicate build number |

### 2. Required Environment Variables
```bash
export APP_STORE_CONNECT_KEY_ID="YOUR_KEY_ID"
export APP_STORE_CONNECT_ISSUER_ID="YOUR_ISSUER_ID"
export APP_STORE_CONNECT_API_KEY_P8="-----BEGIN PRIVATE KEY-----
YOUR_PRIVATE_KEY_CONTENT
-----END PRIVATE KEY-----"
```

### 3. Prerequisites
- Xcode 16.4+ installed
- Node.js 24 and Bun (for web frontend build)
  ```bash
  # Install Bun
  curl -fsSL https://bun.sh/install | bash
  ```
- Rustup; `native/vt-fwd/rust-toolchain.toml` pins the release forwarder toolchain
- GitHub CLI authenticated: `gh auth status`
- Apple Developer ID certificate in Keychain
- Sparkle tools in `~/.local/bin/` (sign_update, generate_appcast)

## 🔐 Sparkle Configuration

### ⚠️ CRITICAL: Sparkle Private Key Management

**ALWAYS use the file-based private key for signing!**

VibeTunnel uses EdDSA signatures for Sparkle updates. The correct private key is stored at:
- `private/sparkle_ed_private_key` (clean key file - REQUIRED for sign_update)
- `private/sparkle_private_key` (commented version for documentation)

**CRITICAL**: The sign_update tool requires a clean key file with ONLY the base64 key. If you only have the commented version, the scripts will automatically extract and create the clean version.

**WARNING**: Your system may have multiple Sparkle private keys:
1. **File-based key** (CORRECT) - Matches the public key in Info.plist
2. **Keychain key** (WRONG) - May produce incompatible signatures

**ALWAYS use the `-f` flag when signing:**
```bash
# ✅ CORRECT - Uses file-based key
sign_update -f private/sparkle_ed_private_key build/VibeTunnel-*.dmg

# ❌ WRONG - May use keychain key
sign_update build/VibeTunnel-*.dmg
```

The public key in Info.plist is: `AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=`

**Key File Format Requirements**:
- The clean key file (`sparkle_ed_private_key`) must contain ONLY the base64 key
- No comments, no extra lines, just the key: `SMYPxE98bJ5iLdHTLHTqGKZNFcZLgrT5Hyjh79h3TaU=`
- The scripts handle this automatically by extracting from the commented file

### Sparkle Requirements for Non-Sandboxed Apps

VibeTunnel is not sandboxed, which simplifies Sparkle configuration:

#### 1. Entitlements (VibeTunnel.entitlements)
```xml

<key>com.apple.security.app-sandbox</key>
<false/>


<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
```

#### 2. Info.plist Configuration
```swift
"SUEnableInstallerLauncherService": false,  // Not needed for non-sandboxed apps
"SUEnableDownloaderService": false,         // Not needed for non-sandboxed apps
```

#### 3. Code Signing Requirements

The notarization script handles all signing correctly:
1. **Do NOT use --deep flag** when signing the app
2. Sign the app with hardened runtime and entitlements

The `notarize-app.sh` script should sign the app:
```bash
# Sign the app WITHOUT --deep flag
codesign --force --sign "Developer ID Application" --entitlements VibeTunnel.entitlements --options runtime VibeTunnel.app
```

### Architecture Support

The native VibeTunnel app supports Apple Silicon (arm64) Macs. The npm package remains available for Intel Macs.

The release build creates one arm64 application and matching embedded server resources. This approach:
- Simplifies distribution with one DMG/ZIP per release
- Works seamlessly with Sparkle auto-updates
- Prevents host-architecture native resources from being embedded in an incompatible app

## 📋 Update Channels

VibeTunnel supports two update channels:

1. **Stable Channel** (`appcast.xml`)
   - Production releases only
   - Default for all users

2. **Pre-release Channel** (`appcast-prerelease.xml`)
   - Includes beta, alpha, and RC versions
   - Users opt-in via Settings

## 🐛 Common Issues and Solutions

### Version and Build Number Issues

#### Double Version Suffix (e.g., 1.0.0-beta.2-beta.2)
**Problem**: Version has double suffix after running release script.

**Cause**: version.xcconfig already had the suffix, and you provided the same suffix to release.sh.

**Solution**: 
1. Clean up the botched release:
   ```bash
   # Delete the bad tag
   git tag -d v1.0.0-beta.2-beta.2
   git push origin :refs/tags/v1.0.0-beta.2-beta.2
   
   # Delete the GitHub release
   gh release delete v1.0.0-beta.2-beta.2 --yes
   
   # Fix version.xcconfig
   # Set to the correct version without double suffix
   ```
2. Re-run the release with correct parameters

#### Build Script Reports Version Mismatch
**Problem**: Build script warns that built version doesn't match version.xcconfig.

**Cause**: Xcode project is not properly configured to use version.xcconfig values.

**Solution**: 
1. Open VibeTunnel.xcodeproj in Xcode
2. Select the project, then the target
3. In Build Settings, ensure:
   - MARKETING_VERSION = `$(MARKETING_VERSION)`
   - CURRENT_PROJECT_VERSION = `$(CURRENT_PROJECT_VERSION)`

#### Preflight Check Warns About Existing Suffix
**Problem**: Preflight check shows "Version already contains pre-release suffix".

**Solution**: This is a helpful warning! It reminds you to use matching parameters:
```bash
# If version.xcconfig has "1.0.0-beta.2"
./scripts/release.sh beta 2  # Correct - matches the suffix
```

### App Size Issues

#### Unexpected Size Increase
**Problem**: DMG size increased significantly (>5MB) between releases.

**Common Causes**:
1. **Development dependencies bundled**: node_modules, JAR files, or other dev files
2. **Build cache not cleaned**: Old artifacts included
3. **New frameworks added**: Legitimate size increase from new dependencies

**Solution**:
```bash
# 1. Check app bundle contents
find build/Build/Products/Release/VibeTunnel.app -name "node_modules" -type d
find build/Build/Products/Release/VibeTunnel.app -name "*.jar" -type f
find build/Build/Products/Release/VibeTunnel.app -type f -size +1M -ls

# 2. Compare with previous release
# Extract previous DMG
hdiutil attach VibeTunnel-previous.dmg
du -sh /Volumes/VibeTunnel/VibeTunnel.app/Contents/*
hdiutil detach /Volumes/VibeTunnel

# 3. Clean and rebuild
./scripts/clean.sh
rm -rf ~/Library/Developer/Xcode/DerivedData/VibeTunnel-*
./scripts/build.sh --configuration Release
```

**Prevention**:
- Add size checks to release script
- Ensure .gitignore includes all development paths
- Regular audits of app bundle contents

### Common Version Sync Issues

#### Web Version Out of Sync
**Problem**: Web server shows different version than macOS app (e.g., "beta.3" when app is "beta.4").

**Cause**: web/package.json was not updated when version.xcconfig was changed.

**Solution**: 
1. Update package.json to match version.xcconfig:
   ```bash
   # Check current versions
   grep MARKETING_VERSION VibeTunnel/version.xcconfig
   grep "version" ../web/package.json
   
   # Update web version to match
   vim ../web/package.json
   ```

2. Validate sync before building:
   ```bash
   cd ../web && node scripts/validate-version-sync.js
   ```

**Note**: The web UI automatically displays the version from package.json (injected at build time).

### "Uncommitted changes detected"
```bash
git status --porcelain  # Check what's changed
git stash              # Temporarily store changes
# Run release
git stash pop          # Restore changes
```

### Appcast Shows HTML Tags Instead of Formatted Text
**Problem**: Sparkle update dialog shows escaped HTML like `&lt;h2&gt;` instead of formatted text.

**Root Cause**: The generate-appcast.sh script is escaping HTML content from GitHub release descriptions.

**Solution**: 
1. Ensure CHANGELOG.md has the proper section for the release version BEFORE running release script
2. The appcast should use local CHANGELOG.md, not GitHub release body
3. If the appcast is wrong, manually fix the generate-appcast.sh script to use local changelog content

### Build Numbers Not Incrementing
**Problem**: Sparkle doesn't detect new version as an update.

**Solution**: Always increment the build number in the Xcode project before releasing.

### Stuck DMG Volumes
**Problem**: "Resource temporarily unavailable" errors when creating DMG.

**Symptoms**:
- `hdiutil: create failed - Resource temporarily unavailable`
- Multiple VibeTunnel volumes visible in Finder
- DMG creation fails repeatedly

**Solution**:
```bash
# Manually unmount all VibeTunnel volumes
for volume in /Volumes/VibeTunnel*; do
    hdiutil detach "$volume" -force
done

# Kill any stuck DMG processes
pkill -f "VibeTunnel.*\.dmg"
```

**Prevention**: Scripts now clean up volumes automatically before DMG creation.

### Build Number Already Exists
**Problem**: Sparkle requires unique build numbers for each release.

**Solution**:
1. Check existing build numbers:
   ```bash
   grep -E '<sparkle:version>[0-9]+</sparkle:version>' ../appcast*.xml
   ```
2. Update `mac/VibeTunnel/version.xcconfig`:
   ```
   CURRENT_PROJECT_VERSION = <new_unique_number>
   ```

### Notarization Failures
**Problem**: App notarization fails or takes too long.

**Common Causes**:
- Missing API credentials
- Network issues
- Apple service outages
- Unsigned frameworks or binaries

**Solution**:
```bash
# Check notarization status
xcrun notarytool history --key-id "$APP_STORE_CONNECT_KEY_ID" \
    --key "$APP_STORE_CONNECT_API_KEY_P8" \
    --issuer-id "$APP_STORE_CONNECT_ISSUER_ID"

# Get detailed log for failed submission
xcrun notarytool log <submission-id> --key-id ...
```

**Normal Duration**: Notarization typically takes 2-10 minutes. If it's taking longer than 15 minutes, check Apple System Status.

### GitHub Release Already Exists
**Problem**: Tag or release already exists on GitHub.

**Solution**: The release script now prompts you to:
1. Delete the existing release and tag
2. Cancel the release

**Prevention**: Always pull latest changes before releasing.

### DMG Shows "Unnotarized Developer ID"
**Problem**: The DMG shows as "Unnotarized Developer ID" when checked with spctl.

**Explanation**: This is NORMAL - DMGs are not notarized themselves, only the app inside is notarized. Check the app inside: it should show "Notarized Developer ID".

### Generate Appcast Fails
**Problem**: `generate-appcast.sh` failed with GitHub API error despite valid authentication.

**Workaround**: 
- Manually add entry to appcast-prerelease.xml
- Use signature from: `sign_update [dmg] --account VibeTunnel`
- Follow existing entry format (see template below)

## 🔧 Troubleshooting Common Issues

### Script Timeouts
If the release script times out:
1. Check `.release-state` for the last successful step
2. Run `./scripts/release.sh --resume` to continue
3. Or manually complete remaining steps (see Manual Recovery below)

### Manual Recovery Steps
If automated release fails after notarization:

1. **Create DMG** (if missing):
   ```bash
   ./scripts/create-dmg.sh build/Build/Products/Release/VibeTunnel.app
   ```

2. **Create GitHub Release**:
   ```bash
   gh release create "v$VERSION" \
     --title "VibeTunnel $VERSION" \
     --notes-file RELEASE_NOTES.md \
     --prerelease \
     build/VibeTunnel-*.dmg \
     build/VibeTunnel-*.zip
   ```

3. **Sign DMG for Sparkle**:
   ```bash
   export SPARKLE_ACCOUNT="VibeTunnel"
   sign_update build/VibeTunnel-$VERSION.dmg --account VibeTunnel
   ```

4. **Update Appcast Manually**:
   - Add entry to appcast-prerelease.xml with signature from step 3
   - Commit and push: `git add appcast*.xml && git commit -m "Update appcast" && git push`

### "Update is improperly signed" Error
**Problem**: Users see "The update is improperly signed and could not be validated."

**Cause**: The DMG was signed with the wrong Sparkle key (default instead of VibeTunnel account).

**Quick Fix**:
```bash
# 1. Download the DMG from GitHub
curl -L -o fix.dmg <github-dmg-url>

# 2. Generate correct signature
sign_update fix.dmg --account VibeTunnel

# 3. Update appcast-prerelease.xml with the new sparkle:edSignature
# 4. Commit and push
```

**Prevention**: The updated scripts now always use `--account VibeTunnel`.

### Debug Sparkle Updates
```bash
# Monitor VibeTunnel logs
log stream --predicate 'process == "VibeTunnel"' --level debug

# Check XPC errors
log stream --predicate 'process == "VibeTunnel"' | grep -i -E "(sparkle|xpc|installer)"

# Verify XPC services
codesign -dvv "VibeTunnel.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc"
```

### Verify Signing and Notarization
```bash
# Check app signature
./scripts/verify-app.sh build/VibeTunnel-1.0.0.dmg

# Verify XPC bundle IDs (should be org.sparkle-project.*)
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" \
  "VibeTunnel.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc/Contents/Info.plist"
```

### Appcast Issues
```bash
# Verify appcast has correct build numbers
./scripts/verify-appcast.sh

# Check if build number is "1" (common bug)
grep '<sparkle:version>' appcast-prerelease.xml
```

## 📝 Appcast Entry Template

```xml
<item>
    <title>VibeTunnel VERSION</title>
    <link>https://github.com/amantus-ai/vibetunnel/releases/download/vVERSION/VibeTunnel-VERSION.dmg</link>
    <sparkle:version>BUILD_NUMBER</sparkle:version>
    <sparkle:shortVersionString>VERSION</sparkle:shortVersionString>
    <description><![CDATA[
        <h2>VibeTunnel VERSION</h2>
        <p><strong>Pre-release version</strong></p>
        
    ]]></description>
    <pubDate>DATE</pubDate>
    <enclosure url="https://github.com/amantus-ai/vibetunnel/releases/download/vVERSION/VibeTunnel-VERSION.dmg" 
               sparkle:version="BUILD_NUMBER" 
               sparkle:shortVersionString="VERSION" 
               length="SIZE_IN_BYTES" 
               type="application/x-apple-diskimage" 
               sparkle:edSignature="SIGNATURE_FROM_SIGN_UPDATE"/>
</item>
```

## 🎯 Release Success Criteria

- [ ] GitHub release created with both DMG and ZIP
- [ ] DMG downloads and mounts correctly
- [ ] App inside DMG shows as notarized
- [ ] Appcast updated and pushed
- [ ] Sparkle signature in appcast matches DMG
- [ ] Version and build numbers correct everywhere
- [ ] Previous version can update via Sparkle

## 🚨 Emergency Fixes

### Wrong Sparkle Signature
```bash
# 1. Get correct signature
sign_update [dmg-url] --account VibeTunnel

# 2. Update appcast-prerelease.xml with correct signature
# 3. Commit and push immediately
```

### Missing from Appcast
```bash
# Users won't see update until appcast is fixed
# Add entry manually following template above
# Test with: curl https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast-prerelease.xml
```

### Build Number Conflict
```bash
# If Sparkle complains about duplicate build number
# Increment build number in version.xcconfig
# Create new release with higher build number
# Old release will be ignored by Sparkle
```

## 🔍 Key File Locations

**Important**: Files are not always where scripts expect them to be.

**Key Locations**:
- **Appcast files**: Located in project root (`/vibetunnel/`), NOT in `mac/`
  - `appcast.xml`
  - `appcast-prerelease.xml`
- **CHANGELOG.md**: Can be in either:
  - `mac/CHANGELOG.md` (preferred by release script)
  - Project root `/vibetunnel/CHANGELOG.md` (common location)
- **Sparkle private key**: Usually in `mac/private/sparkle_private_key`

## 📚 Helper Scripts

### Changelog Management Scripts

#### `generate-release-notes.sh`
Extracts release notes for a specific version from CHANGELOG.md:
```bash
# Get release notes for a specific version
./scripts/generate-release-notes.sh 1.0.0-beta.11

# Works from any directory
cd /tmp && /path/to/scripts/generate-release-notes.sh 1.0.0-beta.11
```

#### `find-changelog.sh`
Reliably locates CHANGELOG.md from any directory:
```bash
# Find the changelog file
./scripts/find-changelog.sh
# Output: /path/to/vibetunnel/CHANGELOG.md
```

#### `fix-release-changelogs.sh`
Updates existing GitHub releases to use per-version changelogs:
```bash
# Dry run to see what would change
./scripts/fix-release-changelogs.sh --dry-run

# Actually update releases
./scripts/fix-release-changelogs.sh

# Update a specific release
./scripts/fix-release-changelogs.sh v1.0.0-beta.11
```

## 📚 Common Commands

### Test Sparkle Signature
```bash
# Find sign_update binary
find . -name sign_update -type f

# Test signing with specific account
./path/to/sign_update file.dmg -f private/sparkle_private_key -p --account VibeTunnel
```

### Verify Appcast URLs
```bash
# Check that appcast files are accessible
curl -I https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast.xml
curl -I https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast-prerelease.xml
```

### Manual Appcast Generation
```bash
# If automatic generation fails
cd mac
export SPARKLE_ACCOUNT="VibeTunnel"
./scripts/generate-appcast.sh
```

### Release Status Script
Create `scripts/check-release-status.sh`:
```bash
#!/bin/bash
VERSION=$1

echo "Checking release status for v$VERSION..."

# Check local artifacts
echo -n "✓ Local DMG: "
[ -f "build/VibeTunnel-$VERSION.dmg" ] && echo "EXISTS" || echo "MISSING"

echo -n "✓ Local ZIP: "
[ -f "build/VibeTunnel-$VERSION.zip" ] && echo "EXISTS" || echo "MISSING"

# Check GitHub
echo -n "✓ GitHub Release: "
gh release view "v$VERSION" &>/dev/null && echo "EXISTS" || echo "MISSING"

# Check appcast
echo -n "✓ Appcast Entry: "
grep -q "$VERSION" ../appcast-prerelease.xml && echo "EXISTS" || echo "MISSING"
```

## 📋 Post-Release Verification

1. **Check GitHub Release**:
   - Verify assets are attached
   - Check file sizes match
   - Ensure release notes are formatted correctly

2. **Test Update in App**:
   - Install previous version
   - Check for updates
   - Verify update downloads and installs
   - Check signature verification in Console.app

3. **Monitor for Issues**:
   - Watch Console.app for Sparkle errors
   - Check GitHub issues for user reports
   - Verify download counts on GitHub

## 🛠️ Recommended Script Improvements

Based on release experience, consider implementing:

### 1. Release Script Enhancements

Add state tracking for resumability:
```bash
# Add to release.sh

# State file to track progress
STATE_FILE=".release-state"

# Save state after each major step
save_state() {
    echo "$1" > "$STATE_FILE"
}

# Resume from last state
resume_from_state() {
    if [ -f "$STATE_FILE" ]; then
        LAST_STATE=$(cat "$STATE_FILE")
        echo "Resuming from: $LAST_STATE"
    fi
}

# Add --resume flag handling
if [[ "$1" == "--resume" ]]; then
    resume_from_state
    shift
fi
```

### 2. Better Progress Reporting
```bash
# Add progress function
progress() {
    local step=$1
    local total=$2
    local message=$3
    echo "[${step}/${total}] ${message}"
}

# Use throughout script
progress 1 8 "Running pre-flight checks..."
progress 2 8 "Building application..."
```

### 3. Parallel Operations
Where possible, run independent operations in parallel:
```bash
# Run signing and changelog generation in parallel
{
    sign_app &
    PID1=$!
    
    generate_changelog &
    PID2=$!
    
    wait $PID1 $PID2
}
```

## 📝 Key Learnings

1. **Always use explicit accounts** when dealing with signing operations
2. **Clean up resources** (volumes, processes) before operations
3. **Verify file locations** - don't assume standard paths
4. **Test the full update flow** before announcing the release
5. **Keep credentials secure** but easily accessible for scripts
6. **Document everything** - future you will thank present you
7. **Plan for long-running operations** - notarization can take 10+ minutes
8. **Implement resumable workflows** - scripts should handle interruptions gracefully
9. **DMG signing is separate from notarization** - DMGs themselves aren't notarized, only the app inside
10. **Command timeouts** are a real issue - use screen/tmux for releases

### Additional Lessons from Recent Releases

#### DMG Notarization Confusion
**Issue**: The DMG shows as "Unnotarized Developer ID" when checked with spctl, but this is normal.
**Explanation**: 
- DMGs are not notarized themselves - only the app inside is notarized
- The app inside the DMG shows correctly as "Notarized Developer ID"
- This is expected behavior and not an error

#### Release Script Timeout Handling
**Issue**: Release script timed out during notarization (took ~5 minutes).
**Solution**: 
- Run release scripts in a terminal without timeout constraints
- Consider using `screen` or `tmux` for long operations
- Add progress indicators to show the script is still running

#### Repository Name Parsing Issue
**Issue**: `generate-appcast.sh` was including `.git` suffix when parsing repository name from git remote URL.
**Fix**: 
- Updated regex to strip `.git` suffix: `${BASH_REMATCH[2]%.git}`
- This caused GitHub API calls to fail with 404 errors
- Always test script changes with actual GitHub API calls

#### Private Key Format Requirements
**Issue**: The sign_update tool fails with "ERROR! Failed to decode base64 encoded key data" when the private key file contains comments.
**Solution**: 
- Create a clean private key file containing ONLY the base64 key: `private/sparkle_ed_private_key`
- The commented key file (`private/sparkle_private_key`) is kept for documentation
- All scripts now use the clean key file automatically
- Scripts will extract the key from the commented file if the clean one doesn't exist

#### State Tracking and Resume Capability
**New Feature**: Release process now supports interruption and resumption.
- Added `release-state.sh` for state management
- Tracks 9 major release steps with progress
- Use `./scripts/release.sh --resume` to continue interrupted release
- Use `./scripts/release.sh --status` to check current state
- State file at `.release-state` contains progress information

## 🚀 Long-term Improvements

1. **CI/CD Integration**: Move releases to GitHub Actions for reliability
2. **Release Dashboard**: Web UI showing release progress and status
3. **Automated Testing**: Test Sparkle updates in CI before publishing
4. **Rollback Capability**: Script to quickly revert a bad release
5. **Release Templates**: Pre-configured release notes and changelog formats
6. **Monitoring Improvements**: Add detailed logging with timestamps and metrics

## Summary

The VibeTunnel release process is complex but well-automated. The main challenges are:
- Command timeouts during long operations (especially notarization)
- Lack of resumability after failures
- Missing progress indicators
- No automated recovery options
- File location confusion

Following this guide and implementing the suggested improvements will make releases more reliable and less stressful, especially when using tools with timeout constraints.

**Remember**: Always use the automated release script, ensure build numbers increment, and test updates before announcing!

## 📚 Important Links

- [Sparkle Sandboxing Guide](https://sparkle-project.org/documentation/sandboxing/)
- [Sparkle Code Signing](https://sparkle-project.org/documentation/sandboxing/#code-signing)
- [Apple Notarization](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution)
- [GitHub Releases API](https://docs.github.com/en/rest/releases/releases)

---

### TESTING EXTERNAL DEVICES (`docs/TESTING_EXTERNAL_DEVICES.md`)

# Testing VibeTunnel on External Devices

This guide explains how to test VibeTunnel development changes on external devices like iPads, iPhones, and other computers.

## Overview

When developing VibeTunnel's web interface, you may encounter browser-specific issues that only appear on certain devices (e.g., Safari on iPad). This guide shows you how to test your local development changes on these devices without deploying.

## Quick Start: Development Server Method

This is the recommended approach for rapid iteration during development.

### 1. Start the Development Server

```bash
cd web
pnpm run dev --port 4021 --bind 0.0.0.0
```

**Key parameters:**
- `--port 4021`: Use a different port than the production server (4020)
- `--bind 0.0.0.0`: Bind to all network interfaces (not just localhost)

### 2. Find Your Mac's IP Address

**Option A: System Preferences**
1. Open System Preferences → Network
2. Select Wi-Fi → Advanced → TCP/IP
3. Look for "IPv4 Address"

**Option B: Terminal**
```bash
# For Wi-Fi connection
ipconfig getifaddr en0

# For Ethernet connection
ipconfig getifaddr en1
```

### 3. Access from External Device

Open a browser on your external device and navigate to:
```
http://[your-mac-ip]:4021
```

Example: `http://192.168.1.42:4021`

## Production Build Method

Use this method when you need to test with the full Mac app integration.

### 1. Build the Web Project

```bash
cd web
pnpm run build
```

### 2. Configure VibeTunnel for Network Access

1. Open VibeTunnel from the menu bar
2. Go to Settings → Dashboard Access
3. Select "Network" mode
4. Set a dashboard password (required for network access)

### 3. Access from External Device

```
http://[your-mac-ip]:4020
```

## Common Issues and Solutions

### Cannot Connect from External Device

**Check network connectivity:**
- Ensure both devices are on the same Wi-Fi network
- Verify the IP address is correct
- Try pinging your Mac from the external device

**Check firewall settings:**
- macOS may block incoming connections
- When prompted, click "Allow" for Node.js/Bun
- Check System Preferences → Security & Privacy → Firewall

**Verify server is running:**
```bash
# Check if the port is listening
lsof -i :4021
```

### Changes Not Appearing

**For development server:**
- Hot reload should work automatically
- Try hard refresh on the external device (Cmd+Shift+R on Safari)
- Check the terminal for build errors

**For production build:**
- You must rebuild after each change: `pnpm run build`
- Restart the VibeTunnel server after building
- Clear browser cache on the external device

### Safari-Specific Issues

**Enable Developer Mode on iOS/iPadOS:**
1. Settings → Safari → Advanced → Web Inspector (ON)
2. Connect device to Mac via USB
3. Open Safari on Mac → Develop menu → [Your Device]
4. Select the page to inspect

**Common Safari quirks:**
- Different touch event handling
- Stricter security policies
- Different viewport behavior
- WebSocket connection issues

## Advanced Testing Scenarios

### Testing with HTTPS

Some features may require HTTPS. Use ngrok for secure tunneling:

```bash
# Install ngrok
brew install ngrok

# Create tunnel to dev server
ngrok http 4021
```

### Testing Different Network Conditions

Use Chrome DevTools or Safari Web Inspector to simulate:
- Slow network connections
- Offline mode
- Different device viewports

### Multi-Device Testing

Test on multiple devices simultaneously:
```bash
# Terminal 1: Development server
pnpm run dev --port 4021 --bind 0.0.0.0

# Terminal 2: Production server (if needed)
# VibeTunnel app handles this automatically
```

## Security Considerations

**Development only:**
- Only use `--bind 0.0.0.0` on trusted networks
- The dev server has no authentication
- Consider using a firewall to restrict access

**Production testing:**
- Always set a dashboard password
- Use Tailscale or ngrok for remote access
- Never expose unprotected servers to the internet

## Debugging Tips

### Console Access on Mobile

**Safari on iOS/iPadOS:**
1. Enable Web Inspector (see above)
2. Connect device to Mac
3. Use Safari Developer Tools

**Chrome on Android:**
1. Enable Developer Options
2. Connect via USB
3. Open chrome://inspect on desktop Chrome

### Network Debugging

Monitor network requests:
```bash
# Watch incoming connections
sudo lsof -i -P | grep LISTEN | grep 4021

# Monitor HTTP traffic
sudo tcpdump -i en0 port 4021
```

### Performance Profiling

Use browser developer tools to:
- Profile JavaScript performance
- Analyze rendering bottlenecks
- Check memory usage
- Monitor WebSocket traffic

## Best Practices

1. **Always test on real devices** - Emulators don't catch all issues
2. **Test on multiple browsers** - Safari, Chrome, Firefox behave differently
3. **Check different orientations** - Portrait and landscape modes
4. **Test with poor network** - Not everyone has fast Wi-Fi
5. **Verify touch interactions** - Mouse events ≠ touch events
6. **Check responsive design** - Different screen sizes and resolutions

## Related Documentation

- [README.md](../README.md) - General setup and usage
- [CONTRIBUTING.md](CONTRIBUTING.md) - Development workflow
- [spec.md](spec.md) - Technical specification
- [architecture.md](architecture.md) - System architecture

---

### Architecture Mario (`docs/architecture-mario.md`)

# 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 `vibetunnel-fwd`) 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 vibetunnel-fwd
    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: WS v3 stream (truncated + multiplexed)
```

### 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 |
| `vibetunnel-fwd` | 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 v3 sends to `/ws` (`INPUT_TEXT`/`INPUT_KEY`/`RESIZE`)
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 asciinema cast file (with write queue for backpressure)
4. CastOutputHub tails file + scans for last clear sequence (pruning)
5. Client receives via `/ws` v3:
   - `STDOUT` (UTF-8 bytes)
   - `SNAPSHOT_VT` (server-rendered VT snapshot bytes)

### 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 `vibetunnel-fwd` 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 `vibetunnel-fwd` 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 `/ws` traffic (v3 frames) for `STDOUT` and `SNAPSHOT_VT`
- Verify pruning works (no replay before last clear)
- Use `/api/sessions/:id/snapshot` for snapshot/export debugging

## 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 `vibetunnel-fwd` 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.

---

### Auggie (`docs/auggie.md`)

# Auggie CLI

VibeTunnel can monitor and control Augment Code's Auggie CLI like other terminal-based coding agents. Auggie sessions are recognized as AI assistant sessions in both the web dashboard and macOS menu, including the action that asks the agent to update its terminal title.

## Install Auggie

Auggie requires Node.js 20 or later. Install the CLI from npm:

```bash
npm install @augmentcode/auggie
```

Authenticate once:

```bash
auggie login
```

See Augment's [installation](https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli) and [authentication](https://docs.augmentcode.com/cli/setup-auggie/authentication) guides for current requirements.

## Run with VibeTunnel

Start an interactive Auggie session through the `vt` wrapper:

```bash
cd /path/to/project
vt auggie
```

You can also pass an initial prompt:

```bash
vt auggie "Review the current changes"
```

The session then appears in VibeTunnel's web, macOS, and mobile clients. Terminal input, output, reconnection, and session controls work the same as for other interactive CLI tools.

## Quick Start

`auggie` is included in the default Quick Start command list. Existing customized lists are preserved; add `auggie` in **Settings > Quick Start** or reset the list to defaults to expose the button.

## Terminal Titles

For an active Auggie session, use the wand action in the web dashboard or macOS menu. VibeTunnel sends a prompt asking Auggie to run:

```bash
vt title "Brief description of current task"
```

This replaces a generic process name with the agent's current task in session lists.

---

### Authentication (`docs/authentication.md`)

# VibeTunnel Authentication System

VibeTunnel supports multiple authentication modes to balance security and convenience for different use cases.

## Authentication Modes

### 1. Default Mode (Password Authentication)

**Usage:** Start VibeTunnel without any auth flags
```bash
npm run dev
# or
./vibetunnel
```

**Behavior:**
- Shows login page with user avatar (on macOS)
- Requires system user password authentication
- Uses JWT tokens for session management
- SSH key functionality is hidden

**Best for:** Personal use with secure password authentication

### 2. SSH Key Mode

**Usage:** Enable SSH key authentication alongside password
```bash
npm run dev -- --enable-ssh-keys
# or
./vibetunnel --enable-ssh-keys
```

**Behavior:**
- Shows login page with both password and SSH key options
- Users can generate Ed25519 SSH keys in the browser
- SSH keys are stored securely in browser localStorage
- Optional password protection for private keys
- SSH keys work for both web and terminal authentication

**Best for:** Power users who prefer SSH key authentication

### 3. SSH Keys Only Mode

**Usage:** Disable password authentication, SSH keys only
```bash
./vibetunnel --disallow-user-password
# or
./vibetunnel --disallow-user-password --enable-ssh-keys  # redundant, auto-enabled
```

**Behavior:**
- Shows login page with SSH key options only
- Password authentication form is hidden
- Automatically enables `--enable-ssh-keys`
- User avatar still displayed with "SSH key authentication required" message
- Most secure authentication mode

**Best for:** High-security environments, organizations requiring key-based auth

### 4. No Authentication Mode

**Usage:** Disable authentication completely
```bash
npm run dev -- --no-auth
# or
./vibetunnel --no-auth
```

**Behavior:**
- Bypasses login page entirely
- Direct access to dashboard
- No authentication required
- Auto-logs in as current system user
- **Overrides all other auth flags**

**Best for:** Local development, trusted networks, or demo environments

### 5. Tailscale Serve Integration Mode

**Usage:** Enable integrated Tailscale Serve support
```bash
npm run dev -- --enable-tailscale-serve
# or
./vibetunnel --enable-tailscale-serve
```

**Behavior:**
- Automatically starts `tailscale serve` as a background process
- Forces server to bind to localhost (127.0.0.1) for security
- Enables Tailscale identity header authentication
- Provides HTTPS access without exposing ports
- No manual Tailscale configuration required

**macOS App Integration:**
- Toggle available in Settings → Remote Access → Tailscale Integration
- Shows HTTPS URL in menu bar when enabled
- Automatically manages the Tailscale Serve process lifecycle

**Security Model:**
- Server only listens on localhost when enabled
- All external access goes through Tailscale's secure proxy
- Identity headers are automatically validated
- No risk of header spoofing from external sources

**Best for:** Easy, secure remote access through Tailscale network

## User Avatar System

### macOS Integration

On macOS, VibeTunnel automatically displays the user's system profile picture:

- **Data Source:** Uses `dscl . -read /Users/$USER JPEGPhoto` to extract avatar
- **Format:** Converts hex data to base64 JPEG
- **Fallback:** Uses `Picture` attribute if JPEGPhoto unavailable
- **Display:** Shows in login form with welcome message

### Other Platforms

On non-macOS systems:
- Displays a generic SVG avatar icon
- Maintains consistent UI layout
- No system integration required

## Command Line Options

### Server Startup Flags

```bash
# Authentication options
--enable-ssh-keys         Enable SSH key authentication UI and functionality
--disallow-user-password  Disable password auth, SSH keys only (auto-enables --enable-ssh-keys)
--no-auth                 Disable authentication (auto-login as current user)
--enable-tailscale-serve  Enable Tailscale Serve integration (auto-starts proxy, forces localhost)

# Other options
--port <number>       Server port (default: 4020)
--bind <address>      Bind address (default: 0.0.0.0)
--debug               Enable debug logging
```

### Example Commands

```bash
# Default password-only authentication
npm run dev

# Enable SSH keys alongside password
npm run dev -- --enable-ssh-keys

# SSH keys only (most secure)
./vibetunnel --disallow-user-password

# No authentication for local development (npm run dev uses this by default)
npm run dev -- --no-auth

# Production with SSH keys on custom port
./vibetunnel --enable-ssh-keys --port 8080

# High-security production (SSH keys only)
./vibetunnel --disallow-user-password --port 8080

# Tailscale Serve integration (secure remote access)
./vibetunnel --enable-tailscale-serve --port 4020
# No manual configuration needed - everything handled automatically
```

## Security Considerations

### Password Authentication
- Uses system PAM authentication
- Uses the configured VibeTunnel username and password instead when both `VIBETUNNEL_USERNAME` and `VIBETUNNEL_PASSWORD` are set
- Sends the password to the VibeTunnel host over the current connection for verification
- Does not persist the password
- Use an encrypted connection; SSH key mode avoids sending a password through the browser
- Otherwise validates against actual system user passwords
- JWT tokens expire after 24 hours
- Secure session management

### SSH Key Authentication
- Generates Ed25519 keys (most secure)
- Private keys stored in browser localStorage
- Optional password protection for private keys
- Keys work for both web and terminal access
- Challenge-response authentication flow

### No Authentication Mode
- **⚠️ Security Warning:** Only use in trusted environments
- Suitable for local development or demo purposes
- Not recommended for production or public networks

### Tailscale Authentication
- **⚠️ Security Warning:** Only use when bound to localhost
- Requires Tailscale Serve proxy for header injection
- Provides SSO-like experience for Tailscale users
- Headers are trusted only from Tailscale proxy

## Configuration API

### Frontend Configuration Endpoint

The frontend can query the server's authentication configuration:

```javascript
// GET /api/auth/config
{
  "enableSSHKeys": false,
  "disallowUserPassword": false,
  "noAuth": false
}
```

This allows the UI to:
- Show/hide SSH key options dynamically
- Hide password form when disallowed
- Skip login page when no-auth is enabled
- Adapt interface based on server configuration

## SSH Key Management

### Key Generation Process
- **Algorithm:** Ed25519 (most secure and modern SSH key type)
- **Browser Implementation:** Uses Web Crypto API for secure key generation
- **Storage:** Browser localStorage (optionally encrypted with user password)
- **Format:** PEM format for compatibility with standard SSH tools
- **Naming:** User-defined names for organization

**Detailed Process:**
1. Browser generates Ed25519 key pair using `crypto.subtle.generateKey()`
2. Private key optionally encrypted with user-provided password
3. Public key formatted in SSH wire format for server validation
4. Keys stored in browser localStorage with unique identifiers

### Key Import
- Supports unencrypted Ed25519 keys in OpenSSH or PKCS#8 PEM format
- Derives and validates the real public key before storing the private key
- Rejects encrypted, malformed, truncated, or internally inconsistent keys with a clear error
- Compatibility with keys generated by `ssh-keygen -t ed25519`

### SSH Key Authentication Flow

**Challenge-Response Process:**
1. **Challenge Request:** Client requests authentication challenge from `/api/auth/challenge`
2. **Challenge Generation:** Server creates 32-byte random challenge with 5-minute expiry
3. **Key Selection:** Client selects appropriate SSH key from browser storage
4. **Signature Creation:** Browser signs challenge using private key via Web Crypto API
5. **Signature Submission:** Client sends signed challenge to `/api/auth/ssh-login`
6. **Server Verification:** 
   - Server parses SSH public key wire format
   - Validates signature using Node.js crypto module
   - Checks public key against user's `~/.ssh/authorized_keys`
   - Issues JWT token upon successful verification

**Key Authorization:**
- Server reads `~/.ssh/authorized_keys` file for target user
- Validates submitted public key is present in authorized keys
- Supports both current user and other system users
- Handles standard SSH authorized_keys format

### Key Setup Instructions

**For Users:**
1. Generate SSH key in VibeTunnel web interface
2. Download public key file
3. Add to server's authorized_keys:
   ```bash
   # Append public key to authorized_keys
   cat vibetunnel-key.pub >> ~/.ssh/authorized_keys
   
   # Set proper permissions
   chmod 600 ~/.ssh/authorized_keys
   chmod 700 ~/.ssh
   ```
4. Test authentication through VibeTunnel login

**Security Best Practices:**
- Use password protection for private keys in shared environments
- Regularly rotate SSH keys (recommended: every 90 days)
- Remove unused keys from authorized_keys
- Monitor authentication logs for suspicious activity

## Tailscale Authentication Details

### How Tailscale Serve Works

Tailscale Serve acts as a reverse proxy that:
1. Receives requests from your tailnet
2. Adds identity headers based on the authenticated Tailscale user
3. Forwards requests to your local service

### Identity Headers

When a request comes through Tailscale Serve, these headers are added:
- `tailscale-user-login`: The user's email address or login
- `tailscale-user-name`: The user's display name
- `tailscale-user-profile-pic`: URL to the user's profile picture

### Setup Instructions

1. **Start VibeTunnel with integrated Tailscale Serve:**
   ```bash
   ./vibetunnel --enable-tailscale-serve --port 4020
   ```
   
   Or use the macOS app and enable the toggle in Settings → Remote Access

2. **Access via Tailscale:**
   ```
   https://[your-machine-name].[tailnet-name].ts.net
   ```

### Security Model

- VibeTunnel trusts identity headers ONLY from localhost connections
- Tailscale Serve ensures headers cannot be spoofed by external users
- Direct access to VibeTunnel port would allow header forgery
- Always bind to `127.0.0.1` when using Tailscale authentication

### Integration with Other Auth Modes

Tailscale Serve integration can be combined with other authentication modes:
```bash
# Tailscale Serve + SSH keys as fallback
./vibetunnel --enable-tailscale-serve --enable-ssh-keys

# Tailscale Serve + local bypass for scripts
./vibetunnel --enable-tailscale-serve --allow-local-bypass
```

**Note**: The `--enable-tailscale-serve` flag automatically manages both the Tailscale proxy and authentication.

## Implementation Details

### Authentication Flow

1. **Server startup** determines available auth modes
2. **Frontend queries** `/api/auth/config` for configuration
3. **UI renders** appropriate authentication options
4. **User authenticates** via chosen method
5. **JWT token issued** for session management
6. **Subsequent requests** use Bearer token authentication

### Avatar Implementation

```bash
# macOS avatar extraction
dscl . -read /Users/$USER JPEGPhoto | tail -1 | xxd -r -p > avatar.jpg

# Server endpoint
GET /api/auth/avatar/:userId
```

### File Structure

```
src/
├── server/
│   ├── middleware/auth.ts       # Authentication middleware
│   ├── routes/auth.ts          # Authentication routes
│   ├── services/auth-service.ts # JWT and user management
│   └── server.ts               # Server configuration
└── client/
    ├── components/auth-login.ts # Login UI component
    ├── services/auth-client.ts  # Frontend auth service
    └── services/ssh-agent.ts    # SSH key management
```


## Troubleshooting

### Common Issues

**Login page shows briefly then disappears (no-auth mode)**
- This is expected behavior - the page quickly redirects to dashboard

**SSH section not showing**
- Ensure server started with `--enable-ssh-keys` flag
- Check browser console for configuration loading errors

**Avatar not displaying**
- macOS only feature - other platforms show generic icon
- Check user has profile picture set in System Preferences

**Authentication fails**
- Verify system password is correct
- Check server logs for detailed error messages
- Ensure proper permissions for PAM authentication

### Debug Mode

Enable debug logging for detailed authentication flow:

```bash
npm run dev -- --debug --enable-ssh-keys
```

This provides verbose logging of:
- Authentication attempts
- Token validation
- SSH key operations
- Configuration loading

---

### Build System (`docs/build-system.md`)

# Build System

VibeTunnel uses platform-specific build systems for each component: Xcode for macOS and iOS applications, pnpm for the web frontend, Cargo for the native forwarder, and Bun for creating standalone executables. The build system supports both development and release builds with comprehensive automation scripts for code signing, notarization, and distribution.

The main build orchestration happens through shell scripts in `mac/scripts/` that coordinate building native applications, bundling the web frontend, and packaging everything together. Release builds include code signing, notarization, DMG creation, and automated GitHub releases with Sparkle update support.

## Build Workflows

### macOS Application Build

**Development Build with Poltergeist** (Recommended):
```bash
# Start Poltergeist for automatic rebuilds
poltergeist
# Make changes - app rebuilds automatically
```

**Development Build without Poltergeist**:
```bash
cd mac
xcodebuild -project VibeTunnel.xcodeproj -scheme VibeTunnel -configuration Debug build
```

**Release Build** - Full build with code signing:
```bash
cd mac
./scripts/build.sh --configuration Release --sign
```

**Key Script**: `mac/scripts/build.sh` (lines 39-222)
- Builds Bun executable from web frontend
- Compiles macOS app using xcodebuild
- Handles code signing if requested
- Verifies version consistency with `mac/VibeTunnel/version.xcconfig`

### Web Frontend Build

**Development Mode** - Watch mode with hot reload:
```bash
cd web
pnpm run dev
```

**Production Build** - Optimized bundles:
```bash
cd web
pnpm run build
```

**Bun Executable** - Standalone binary with native modules:
```bash
cd web
node build-native.js
```

**Key Files**:
- `web/package.json` - Build scripts and dependencies (lines 6-34)
- `web/build-native.js` - Bun compilation and native module bundling (lines 83-135)

### Native Forwarder Build

Rustup selects Rust 1.97.0 from `native/vt-fwd/rust-toolchain.toml`:

```bash
cd native/vt-fwd
cargo build --release --locked
```

The binary is written to `native/vt-fwd/target/release/vibetunnel-fwd`. Web and macOS build scripts copy it into their packaged resources.

### iOS Application Build

**Generate Xcode Project** - From project.yml:
```bash
cd ios
xcodegen generate
```

**Build via Xcode** - Open `ios/VibeTunnel.xcodeproj` and build

**Key File**: `ios/project.yml` - XcodeGen configuration (lines 1-92)

### Release Workflow

**Complete Release** - Build, sign, notarize, and publish:
```bash
cd mac
./scripts/release.sh stable           # Stable release
./scripts/release.sh beta 1          # Beta release
```

**Key Script**: `mac/scripts/release.sh` (lines 1-100+)
- Validates environment and dependencies
- Builds with appropriate flags
- Signs and notarizes app
- Creates DMG
- Publishes GitHub release
- Updates Sparkle appcast

## Platform Setup

### macOS Requirements

**Development Tools**:
- Xcode 16.0+ with command line tools
- Node.js 22.12 through 24.x and pnpm
- Bun runtime (installed via npm)
- Rustup; the forwarder toolchain is pinned by `native/vt-fwd/rust-toolchain.toml`
- xcbeautify (optional, for cleaner output)

**Release Requirements**:
- Valid Apple Developer certificate
- App Store Connect API keys for notarization
- Sparkle EdDSA keys in `mac/private/`

**Configuration Files**:
- `apple/Local.xcconfig` - Local development settings
- `mac/VibeTunnel/version.xcconfig` - Version numbers
- `mac/Shared.xcconfig` - Shared build settings

### Web Frontend Requirements

**Tools**:
- Node.js 22.12 through 24.x with npm
- Bun runtime for standalone builds
- Rustup for native forwarder builds

**Native Modules**:
- `@homebridge/node-pty-prebuilt-multiarch` - Terminal emulation
- Platform-specific binaries in `web/native/`:
  - `pty.node` - Native PTY module
  - `spawn-helper` - Process spawning helper
  - `vibetunnel` - Bun executable
  - `vibetunnel-fwd` - Rust terminal forwarder

### Linux (Ubuntu) Requirements

**Packages**:
- `curl`, `ca-certificates`, `xz-utils`, `python3`, `make`, `g++`, `git`
- `libpam0g-dev` (authenticate-pam native module)

**Tooling**:
- Node.js 22.12 through 24.x (NodeSource 24.x recommended)
- Rust 1.97.0 (pinned by `native/vt-fwd/rust-toolchain.toml`)

**Bootstrap**:
```bash
web/scripts/linux-bootstrap.sh
```

### iOS Requirements

**Tools**:
- Xcode 16.0+
- XcodeGen (install via Homebrew)
- iOS 18.0+ deployment target

**Dependencies**:
- ghostty-web resources (JS + WASM) bundled in the iOS app

## Reference

### Build Targets

**macOS Xcode Workspace** (`mac/VibeTunnel.xcworkspace`):
- VibeTunnel scheme - Main application
- Debug configuration - Development builds
- Release configuration - Distribution builds

**Web Build Scripts** (`web/package.json`):
- `dev` - Development server with watchers
- `build` - Production TypeScript compilation
- `bundle` - Client-side asset bundling
- `typecheck` - TypeScript validation
- `lint` - ESLint code quality checks

### Build Scripts

**Core Build Scripts** (`mac/scripts/`):
- `build.sh` - Main build orchestrator
- `build-bun-executable.sh` - Bun compilation (lines 31-92)
- `copy-bun-executable.sh` - Bundle integration
- `codesign-app.sh` - Code signing
- `notarize-app.sh` - Apple notarization
- `create-dmg.sh` - DMG packaging
- `generate-appcast.sh` - Sparkle updates

**Helper Scripts**:
- `preflight-check.sh` - Pre-build validation
- `version.sh` - Version management
- `clean.sh` - Build cleanup
- `verify-app.sh` - Post-build verification

### Troubleshooting

**Common Issues**:

1. **Bun build fails** - Check `web/build-native.js` patches (lines 11-79)
2. **Code signing errors** - Verify `apple/Local.xcconfig` settings
3. **Notarization fails** - Check API keys in environment
4. **Version mismatch** - Update `mac/VibeTunnel/version.xcconfig`

**Build Artifacts**:
- macOS app: `mac/build/Build/Products/Release/VibeTunnel.app`
- Web bundles: `web/public/bundle/`
- Native executables: `web/native/`
- iOS app: `ios/build/`

**Clean Build**:
```bash
cd mac && ./scripts/clean.sh
cd ../web && npm run clean
```

---

### Changelog Management (`docs/changelog-management.md`)

# Changelog Management Guide

This guide explains how to maintain the CHANGELOG.md and GitHub releases for VibeTunnel.

## Overview

We maintain a comprehensive changelog that:
- Documents all user-facing changes
- Credits contributors properly with GitHub links
- Tracks first-time contributors for each release
- Provides clear, user-friendly descriptions

## Using the `/changelog` Command

The `/changelog` command in Claude Code analyzes git history to generate comprehensive changelogs:

```bash
# In Claude Code, simply type:
/changelog
```

This command:
- Analyzes commit history beyond just titles
- Examines actual file changes to understand user impact
- Groups changes by category (Features, Bug Fixes, Performance, etc.)
- Writes user-focused descriptions (not developer jargon)

## Changelog Format

### Version Header
```markdown
## [1.0.0-beta.13] - 2024-12-20
```

### Categories
- **Features** - New functionality
- **Improvements** - Enhancements to existing features
- **Bug Fixes** - Fixed issues
- **Performance** - Speed/efficiency improvements
- **Developer Experience** - Build, test, or development improvements

### Attribution Format

Every change should credit its contributor:
```markdown
- Added systemd service management for Linux deployments (via [@hewigovens](https://github.com/hewigovens)) (#419)
```

Format: `- Description (via [@username](https://github.com/username)) (#PR)`

### First-time Contributors Section

For releases with new contributors:
```markdown
### First-time Contributors
- [@hewigovens](https://github.com/hewigovens) - Added systemd service management for Linux (#419)
```

## Identifying Contributors

### Finding First-time Contributors

```bash
# Get all contributors up to a specific release
git log --format="%an|%ae" v1.0.0-beta.12 | sort -u

# Get contributors for a specific release
git log --format="%an|%ae" v1.0.0-beta.12..v1.0.0-beta.13 | sort -u

# Compare to find first-timers
```

### Mapping Changes to Contributors

```bash
# Find who made specific changes
git log --oneline --author="username" v1.0.0-beta.12..v1.0.0-beta.13

# Get detailed commit info with files
git log --stat --author="username" v1.0.0-beta.12..v1.0.0-beta.13
```

## Special Cases

### Bot Contributors
Do not highlight bot contributors as first-time contributors or include them in the contributors list:
- `devin-ai-integration[bot]`
- `blacksmith-sh[bot]`
- Other `*[bot]` accounts

**Important**: Bot contributions should be completely excluded from:
- First-time contributors sections
- The main contributors list at the end of CHANGELOG.md
- GitHub release notes contributors sections

Bot changes can be mentioned in regular changelog entries (e.g., "Added SwiftLint hooks") but without attribution.

### Core Team
Core team members (repository owners) don't need "(via @username)" attribution unless specifically requested.

### Multiple Contributors
If multiple people worked on a feature:
```markdown
- Feature description (via [@user1](https://github.com/user1), [@user2](https://github.com/user2)) (#123)
```

## GitHub Releases

### Creating a Release

1. **Generate changelog** using `/changelog` command
2. **Review and edit** the generated content
3. **Update CHANGELOG.md** with the new version section
4. **Create GitHub release**:
   ```bash
   gh release create v1.0.0-beta.14 \
     --title "v1.0.0-beta.14" \
     --notes-file release-notes.md \
     --prerelease
   ```

### Release Notes Format

The GitHub release should include:

1. **Highlights** - 2-3 major changes
2. **Full changelog** - Copy from CHANGELOG.md
3. **First-time contributors** - If applicable
4. **Installation instructions** - Brief reminder

Example:
```markdown
## Highlights
- 🐧 Linux systemd service support for production deployments
- 🔧 Improved authentication reliability
- 🚀 Better performance for large terminal outputs

## What's Changed
[Copy from CHANGELOG.md]

## First-time Contributors
- @hewigovens made their first contribution in #419

## Installation
See [installation instructions](https://github.com/vibetunnel/vibetunnel#installation)
```

### Updating Existing Releases

To add first-time contributors to existing releases:

```bash
# Edit a release
gh release edit v1.0.0-beta.13 --notes-file updated-notes.md

# Or use the GitHub web UI
```

## Workflow Summary

1. **Before release**: Run `/changelog` to analyze changes
2. **Review output**: Ensure proper attribution and user-friendly descriptions
3. **Update CHANGELOG.md**: Add new version section with proper formatting
4. **Create release notes**: Include highlights and first-time contributors
5. **Create GitHub release**: Use `gh release create` or web UI
6. **Verify**: Check that all contributors are properly credited

## Tips

- Always verify contributor GitHub usernames for correct links
- Use clear, non-technical language in descriptions
- Include PR numbers for traceability
- Group related changes together
- Highlight breaking changes prominently
- Credit everyone who contributed, no matter how small

## Example Workflow

```bash
# 1. Generate changelog
/changelog

# 2. Create release notes file
cat > release-notes.md << 'EOF'
## Highlights
- 🎯 Major feature one
- 🐛 Critical bug fix
- ⚡ Performance improvement

## What's Changed
[Paste from CHANGELOG.md]

## First-time Contributors
- @newcontributor made their first contribution in #123

## Installation
See [installation instructions](https://github.com/vibetunnel/vibetunnel#installation)
EOF

# 3. Create release
gh release create v1.0.0-beta.14 \
  --title "v1.0.0-beta.14" \
  --notes-file release-notes.md \
  --prerelease

# 4. Clean up
rm release-notes.md
```

---

### Cjk Ime Input (`docs/cjk-ime-input.md`)

# VibeTunnel CJK IME Input Implementation

## Overview

VibeTunnel provides comprehensive Chinese, Japanese, and Korean (CJK) Input Method Editor (IME) support across both desktop and mobile platforms. The implementation uses platform-specific approaches to ensure optimal user experience:

- **Desktop**: Cursor-positioned text input with native browser IME integration
- **Mobile**: Native virtual keyboard with direct input handling

## Architecture

### Core Components

```
SessionView
├── InputManager (Main input coordination layer)
│   ├── Platform detection (mobile vs desktop)
│   ├── DesktopIMEInput component integration (desktop only)
│   ├── Keyboard input handling
│   ├── WebSocket/HTTP input routing
│   └── Terminal cursor position access
├── DesktopIMEInput (Desktop-specific IME component)
│   ├── Cursor-positioned input element creation
│   ├── IME composition event handling
│   ├── Global paste handling
│   ├── Dynamic cursor positioning
│   └── Focus management
├── DirectKeyboardManager (Mobile input handling)
│   ├── Native virtual keyboard integration
│   ├── Direct input processing
│   └── Quick keys toolbar
├── LifecycleEventManager (Event interception & coordination)
└── Terminal Components (Cursor position providers)
```

## Implementation Details

### Cursor Position Tracking

**File**: `cursor-position.ts`

The cursor position tracking system uses renderer-specific cursor coordinates:

#### Coordinate System

```typescript
export function calculateCursorPosition(
  cursorX: number, // 0-based column position
  cursorY: number, // 0-based row position
  fontSize: number, // Terminal font size in pixels
  container: Element, // Terminal container element
  sessionStatus: string, // Session status for validation
): { x: number; y: number } | null;
```

#### Position Calculation Process

1. **Character Measurement**: Dynamically measures actual character width using font metrics
2. **Absolute Positioning**: Calculates page-absolute cursor coordinates
3. **Container Relative**: Converts to position relative to `#session-terminal` container
4. **IME Positioning**: Returns coordinates suitable for IME input placement

#### Terminal Type Support

- **Ghostty Terminal (`vibe-terminal`)**: Uses the active buffer cursor and renderer cell metrics.
- **Buffer Terminal (`vibe-terminal-buffer`)**: Uses `buffer.cursorX/Y` from VT snapshot data.

#### Key Features

- **Precise Alignment**: Accounts for exact character width and line height
- **Container Aware**: Handles side panels and complex layouts
- **Font Responsive**: Adapts to different font sizes and families
- **Platform Consistent**: Same calculation logic across all terminal types

#### Error Handling

The function includes comprehensive error handling and graceful fallbacks:

- Returns `null` when session is not running
- Returns `null` when container element is not found
- Returns `null` when character measurement fails
- Falls back to absolute coordinates if session container is missing

### Platform Detection

**File**: `mobile-utils.ts`

VibeTunnel automatically detects the platform and chooses the appropriate IME strategy:

```typescript
export function detectMobile(): boolean {
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
    navigator.userAgent,
  );
}
```

### Desktop Implementation

#### 1. DesktopIMEInput Component

**File**: `ime-input.ts`

A dedicated component for desktop browsers that creates and manages a native text input:

- Positioned dynamically at terminal cursor location
- Uses a normal input size so browsers can anchor native candidate windows reliably
- Transparent background and hidden caret keep the terminal visually unobstructed
- Handles all CJK composition events through standard DOM APIs
- Focuses when the user clicks the terminal, with short animation-frame retries for browser timing
- Clean lifecycle management with proper cleanup

#### 2. Desktop Input Manager Integration

**File**: `input-manager.ts`

The `InputManager` detects platform and creates the appropriate IME component:

```typescript
private setupIMEInput(): void {
  // Skip IME input setup on mobile devices (they use native keyboard)
  if (detectMobile()) {
    logger.log('Skipping IME input setup on mobile device');
    return;
  }

  // Create desktop IME input component
  this.imeInput = new DesktopIMEInput({
    container: terminalContainer,
    onTextInput: (text: string) => this.sendInputText(text),
    onSpecialKey: (key: string) => this.sendInput(key),
    getCursorInfo: () => {
      const terminalElement = this.callbacks?.getTerminalElement?.();
      if (
        terminalElement &&
        'getCursorInfo' in terminalElement &&
        typeof terminalElement.getCursorInfo === 'function'
      ) {
        return terminalElement.getCursorInfo();
      }
      return null;
    }
  });
}
```

#### 3. Desktop Focus Management

**File**: `ime-input.ts`

Desktop IME focus follows terminal clicks. The implementation avoids polling because repeatedly stealing focus interferes with native candidate selection:

```typescript
focus(): void {
  this.updatePosition();
  this.input.focus();

  requestAnimationFrame(() => {
    if (document.activeElement !== this.input) {
      requestAnimationFrame(() => this.input.focus());
    }
  });
}
```

### Mobile Implementation

#### 1. Direct Keyboard Manager

**File**: `direct-keyboard-manager.ts`

Mobile devices use the native virtual keyboard with a visible input field:

- Standard HTML input element (not hidden)
- Native virtual keyboard with CJK support
- Quick keys toolbar for common terminal operations
- No special IME handling needed (OS provides it)

#### 2. Mobile Input Flow

**Files**: `session-view.ts`, `lifecycle-event-manager.ts`

Mobile input handling follows a different flow:

1. User taps terminal area
2. Native virtual keyboard appears with CJK support
3. User types or selects from IME candidates
4. Input is sent directly to terminal
5. No desktop composition bridge is needed

## Platform Differences

### Key Implementation Differences

| Aspect               | Desktop                          | Mobile                       |
| -------------------- | -------------------------------- | ---------------------------- |
| **Input Element**    | Cursor-positioned standard input | Visible standard input field |
| **IME Handling**     | Custom composition events        | Native OS keyboard           |
| **Positioning**      | Follows terminal cursor          | Fixed position or overlay    |
| **Focus Management** | Click focus with bounded retries | Standard focus behavior      |
| **Keyboard**         | Physical + software IME          | Virtual keyboard with IME    |
| **Integration**      | Transparent terminal overlay     | Visible UI component         |
| **Performance**      | Minimal overhead                 | Standard input performance   |

### Technical Architecture Differences

#### Desktop Implementation

```typescript
// Creates a browser-compatible input at the terminal cursor
const input = document.createElement("input");
input.style.width = "200px";
input.style.height = "24px";
input.style.backgroundColor = "transparent";
input.style.caretColor = "transparent";

// Handles IME composition events
input.addEventListener("compositionstart", handleStart);
input.addEventListener("compositionend", handleEnd);

// Positions at terminal cursor
input.style.left = `${cursorX}px`;
input.style.top = `${cursorY}px`;
```

#### Mobile Implementation

```typescript
// Uses DirectKeyboardManager with visible input
const input = document.createElement("input");
input.type = "text";
input.placeholder = "Type here...";
// Standard visible input - no special IME handling needed

// OS handles IME automatically through virtual keyboard
// No composition event handling required
```

### User Experience Differences

#### Desktop Experience

- **Seamless**: No visible UI changes
- **Cursor following**: IME popup appears at terminal cursor
- **Click to focus**: Click anywhere in terminal area
- **Traditional**: Works like native terminal IME
- **Paste support**: Global paste handling anywhere in terminal

#### Mobile Experience

- **Touch-first**: Designed for finger interaction
- **Visible input**: Clear indication of where to type
- **Quick keys**: Easy access to terminal-specific keys
- **Gesture support**: Touch gestures and haptic feedback
- **Keyboard management**: Handles virtual keyboard show/hide

## Platform-Specific Features

### Desktop Features

- **Dynamic cursor positioning**: IME popup follows terminal cursor exactly
- **Global paste handling**: Paste works anywhere in terminal area
- **Composition state tracking**: Via native `KeyboardEvent.isComposing` plus the `data-ime-composing` DOM attribute
- **Focus management**: Click focus plus bounded browser-timing retries
- **Transparent integration**: Native input text appears at the terminal cursor without a separate control
- **Performance optimized**: Minimal resource usage when not composing

### Mobile Features

- **Native virtual keyboard**: Full OS-level CJK IME integration
- **Quick keys toolbar**: Touch-friendly terminal keys (Tab, Esc, Ctrl, etc.)
- **Touch-optimized UI**: Larger tap targets and touch gestures
- **Auto-capitalization control**: Intelligently disabled for terminal accuracy
- **Viewport management**: Graceful handling of keyboard show/hide animations
- **Direct input mode**: Option to use hidden input for power users

## User Experience

### Desktop Workflow

```
User clicks terminal → Cursor-positioned input focuses → Types CJK →
Browser shows IME candidates → User selects → Text appears in terminal
```

### Mobile Workflow

```
User taps terminal → Virtual keyboard appears → Types CJK →
OS shows IME candidates → User selects → Text appears in terminal
```

### Visual Behavior

- **Desktop**: Transparent input text and native IME popup at the terminal cursor
- **Mobile**: Standard input field with native virtual keyboard
- **Both platforms**: Seamless CJK text input with full IME support

## Performance

### Optimization Features

- One input element and listener set per active desktop session
- Dynamic positioning only calculated when needed
- Minimal DOM footprint (single input element)
- Clean event delegation and lifecycle management
- Click-to-focus behavior without polling
- Proper cleanup prevents memory leaks during session changes

## Code Reference

### Primary Files

- `ime-input.ts` - Desktop input creation, composition events, positioning, focus, paste, and cleanup
- `input-manager.ts` - Input coordination, desktop/mobile selection, terminal routing, and lifecycle
- `terminal.ts` - Ghostty renderer cursor geometry used to anchor the native input
- `lifecycle-event-manager.ts` - Keyboard interception that leaves active composition to the browser
- `direct-keyboard-manager.ts` - Mobile keyboard handling
- `mobile-utils.ts` - Mobile detection utilities

### Supporting Files

- `session-view.ts` - Container element and terminal integration
- `ime-constants.ts` - IME-related key filtering utilities
- `terminal-constants.ts` - Terminal element IDs, font settings, and IME positioning constants

## Browser Compatibility

Works with all major browsers that support:

- IME composition events (`compositionstart`, `compositionupdate`, `compositionend`)
- Clipboard API for paste functionality
- Standard DOM positioning APIs

The affected macOS boundary has live coverage with Chrome and Safari using Chinese Pinyin. Automated coverage verifies the composition lifecycle and cursor anchoring independently of the operating-system candidate UI.

## Configuration

### Automatic Platform Detection

CJK IME support is automatically configured based on the detected platform:

- **Desktop**: Cursor-positioned IME input with native candidate-window anchoring
- **Mobile**: Native virtual keyboard with OS IME

### Requirements

1. User has CJK input method enabled in their OS
2. Desktop: User clicks in terminal area to focus
3. Mobile: User taps terminal or input field
4. User switches to CJK input mode in their OS

## Troubleshooting

### Common Issues

- **IME candidates not showing**: Ensure browser supports composition events
- **Text not appearing**: Check if terminal session is active and receiving input
- **Paste not working**: Verify clipboard permissions in browser

### Debug Information

Comprehensive logging available in browser console:

- `🔍 Setting up IME input on desktop device` - Platform detection
- `[ime-input]` - Desktop IME component events
- `[direct-keyboard-manager]` - Mobile keyboard events
- State tracking through DOM attributes:
  - `data-ime-composing` - IME composition active (desktop)
  - `data-ime-input-focused` - IME input has focus (desktop)
- Mobile detection logs showing user agent analysis

---

## Recent Improvements (v1.0.0-beta.16+)

### Unified Cursor Position Tracking

- **Shared Utility**: Created `cursor-position.ts` for consistent cursor calculation across all terminal types
- **Container-Aware Positioning**: Fixed IME positioning issues with side panels and complex layouts
- **Precise Alignment**: Improved character width measurement for pixel-perfect cursor alignment
- **Debug Logging**: Enhanced debug output with comprehensive coordinate information

### Technical Improvements

- **Code Deduplication**: Eliminated ~120 lines of duplicate cursor calculation code
- **Maintainability**: Single source of truth for cursor positioning logic
- **Type Safety**: Improved TypeScript interfaces and error handling
- **Performance**: More efficient coordinate conversion with optimized calculations

### Element ID Centralization

- **Constants File**: Created `terminal-constants.ts` to centralize all critical terminal element IDs
- **Prevention of Breakage**: Changes to IDs like `session-terminal`, `buffer-container`, or `terminal-container` now only require updates in one location
- **Consistent References**: All components now import `TERMINAL_IDS` constants instead of using hardcoded strings
- **Type Safety**: Constants are strongly typed to prevent typos and ensure consistent usage across the codebase

---

**Status**: ✅ Production Ready  
**Platforms**: Desktop (Windows, macOS, Linux) and Mobile (iOS, Android)  
**Version**: VibeTunnel Web v1.0.0-beta.16+  
**Last Updated**: 2026-06-13

---

### Claude (`docs/claude.md`)

# Claude CLI Usage Guide

The Claude CLI is a powerful command-line interface for interacting with Claude. This guide covers basic usage, advanced features, and important considerations when using Claude as an agent.

## Installation

```bash
# Install via npm
npm install @anthropic/claude-cli

# Or use directly with npx
npx @anthropic/claude-cli
```

## Basic Usage

### Recommended: Use VibeTunnel for Better Visibility

When working within VibeTunnel, use `vt claude` instead of `claude` directly. This provides better visibility into what Claude is doing:

```bash
# Use vt claude for better monitoring
vt claude "What is the capital of France?"

# VibeTunnel will show Claude's activities in real-time
vt claude -f src/*.js "Refactor this code"
```

### One-Shot Prompts

```bash
# Simple question
vt claude "What is the capital of France?"

# Multi-line prompt with quotes
vt claude "Explain the following code:
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}"
```

### Input Methods

```bash
# Pipe input from another command
echo "Hello, world!" | vt claude "Translate this to Spanish"

# Read prompt from file
vt claude < prompt.txt

# Use heredoc for complex prompts
vt claude << 'EOF'
Analyze this SQL query for performance issues:
SELECT * FROM users WHERE created_at > '2023-01-01'
EOF
```

### File Context

```bash
# Single file context
vt claude -f script.js "Explain what this script does"

# Multiple files
vt claude -f src/*.js -f tests/*.test.js "Find potential bugs"

# With explicit file references
vt claude -f config.json -f app.js "How does the app use the config?"
```

## Advanced Features

### Timeout Considerations

⏱️ **Important**: Claude can be slow but thorough. When calling Claude from scripts or other tools, **set a timeout of more than 10 minutes**:

```bash
# Example: Using timeout command
timeout 900s vt claude -f "*.js" "Refactor this codebase"

# Example: In a Python script
subprocess.run(['vt', 'claude', 'analyze this'], timeout=900)

# Example: In a Node.js script
await exec('vt claude "complex task"', { timeout: 900000 })  // milliseconds
```

Claude may take time to:
- Analyze large codebases thoroughly
- Consider multiple approaches before responding
- Verify its suggestions are correct
- Generate comprehensive solutions

**Note**: Claude itself has no built-in timeout mechanism. The calling process must implement timeout handling.

⚠️ **Critical**: Even if a timeout occurs, Claude may have already modified multiple files before being interrupted. After any Claude invocation (successful or timed out):

1. **Re-read all files** that were passed to Claude
2. **Check related files** that Claude might have modified (imports, dependencies, tests)
3. **Use version control** to see what changed: `git status` and `git diff`
4. **Never assume** the operation failed completely - partial changes are common

```bash
# Example: Safe Claude invocation pattern
git add -A  # Stage current state
timeout 900s vt claude -f src/*.js "Refactor error handling" || true
git status  # See what changed
git diff    # Review all modifications

# In scripts: Always check for changes
vt claude -f config.json "Update settings" || echo "Claude timed out"
# Still need to check if config.json was modified!
```

### Environment Variables

```bash
# Set API key
export ANTHROPIC_API_KEY="your-key-here"

# Set model (if supported)
export CLAUDE_MODEL="claude-3-opus-20240229"

```

### Output Formatting

```bash
# Save response to file
vt claude "Write a Python hello world" > hello.py

# Append to file
vt claude "Add error handling" >> hello.py

# Process output with other tools
vt claude "List 10 programming languages" | grep -i python
```

### Interactive Mode

```bash
# Start interactive session
vt claude -i

# With initial context
vt claude -i -f project.md "Let's work on this project"
```

## Important Considerations: Claude as an Agent

⚠️ **Critical Understanding**: Claude is an intelligent agent that aims to be helpful and thorough. This means:

### Default Behavior

When you use Claude via CLI, it will:
- **Analyze the full context** of your request
- **Make reasonable inferences** about what you need
- **Perform additional helpful actions** beyond the literal request
- **Verify and validate** its work
- **Provide explanations** and context

### Example of Agent Behavior

```bash
# What you ask:
vt claude -f buggy.js "Fix the syntax error on line 5"

# What Claude might do:
# 1. Fix the syntax error on line 5
# 2. Notice and fix other syntax errors
# 3. Identify potential bugs
# 4. Suggest better practices
# 5. Format the code
# 6. Add helpful comments
```

### Controlling Agent Behavior

If you need Claude to perform **ONLY** specific actions without additional help:

#### Strict Mode Prompting

```bash
# Explicit constraints
vt claude -f config.json "Change ONLY the 'port' value to 8080. 
Make NO other changes. 
Do NOT fix any other issues you might notice.
Do NOT add comments or formatting.
Output ONLY the modified line."

# Surgical edits
vt claude -f script.sh "Replace EXACTLY the string 'localhost' with '0.0.0.0' on line 23.
Make NO other modifications to the file.
Do NOT analyze or improve the script."
```

#### Best Practices for Strict Operations

1. **Be explicit about constraints**:
   ```bash
   vt claude "List EXACTLY 3 items. No more, no less. No explanations."
   ```

2. **Use precise language**:
   ```bash
   # Instead of: "Fix the typo"
   # Use: "Change 'recieve' to 'receive' on line 42 ONLY"
   ```

3. **Specify output format**:
   ```bash
   vt claude "Output ONLY valid JSON, no markdown formatting, no explanations"
   ```

4. **Chain commands for control**:
   ```bash
   # Use grep/sed for deterministic edits instead of Claude
   vt claude -f file.py "Find the typo" | grep -n "recieve"
   sed -i 's/recieve/receive/g' file.py
   ```

## Use Cases

### When to Use Claude's Agent Capabilities

- **Code review**: Let Claude analyze thoroughly
- **Debugging**: Benefit from comprehensive analysis
- **Learning**: Get detailed explanations
- **Refactoring**: Allow intelligent improvements

### When to Constrain Claude

- **CI/CD pipelines**: Need deterministic behavior
- **Automated scripts**: Require predictable outputs
- **Specific edits**: Want surgical precision
- **Integration with other tools**: Need exact output formats

## Examples

### Development Workflow

```bash
# Let Claude be helpful (default)
vt claude -f app.js -f test.js "Add error handling"

# Constrained for automation
vt claude -f config.yml "Output ONLY the value of 'database.host'. No formatting." > db_host.txt
```

### Script Integration

```bash
#!/bin/bash
# Get exactly what you need
PORT=$(vt claude -f config.json "Print ONLY the port number. Nothing else.")
echo "Server will run on port: $PORT"
```

## Tips

1. **Test first**: Always test Claude's behavior before using in automation
2. **Be explicit**: Over-specify when you need exact behavior
3. **Use version control**: Claude might make helpful changes you didn't expect
4. **Review outputs**: Especially in automated workflows
5. **Leverage intelligence**: Don't over-constrain when you want smart help

## Command Reference

```bash
vt claude --help              # Show all options
vt claude --version          # Show version
vt claude -f FILE            # Include file context
vt claude -i                 # Interactive mode
vt claude --no-markdown      # Disable markdown formatting
vt claude --json             # JSON output (if supported)
```

**Note**: When not using VibeTunnel, replace `vt claude` with just `claude` in all commands above.

Remember: Claude is designed to be a helpful assistant. This is usually what you want, but sometimes you need precise, limited actions. Plan accordingly!

---

### Custom Node (`docs/custom-node.md`)

# Custom Node.js Build

## Motivation

VibeTunnel uses Node.js Single Executable Applications (SEA) to create a standalone terminal server. However, the standard Node.js binary is quite large:

- **Standard Node.js binary**: ~110MB
- **Custom minimal Node.js**: ~68MB (38% reduction)
- **Final executable size**: ~72MB
- **Final app size impact**: Saves roughly 40MB compared with embedding the standard runtime

We don't need many Node.js features for VibeTunnel:
- Small ICU data is sufficient for Unicode-aware JavaScript and English locale data
- No npm package manager in the binary
- No inspector/debugging protocol
- No V8 snapshots or code cache

By building a custom Node.js with small ICU and without the remaining features, we achieve a significantly smaller app bundle while maintaining full functionality.

## Build Behavior

### Debug Mode (Xcode)
- Uses system Node.js for faster iteration
- No custom Node.js compilation required
- Build output shows: `"Debug build - using system Node.js for faster builds"`
- If a custom Node.js was previously built, it will be reused for consistency

### Release Mode (Xcode)
- Automatically builds custom minimal Node.js on first run
- Compilation takes 10-20 minutes but is cached for future builds
- Uses the custom Node.js to create a smaller executable
- Build output shows version and size comparison

## Prerequisites

### Required Build Tools
For optimal build performance, the following tools are required:
- **Ninja**: Build system for faster compilation (significantly faster than Make)
- **ccache**: Compiler cache to speed up rebuilds

#### Installation
- **macOS**: `brew install ninja ccache`
- **Linux**: `apt-get install ninja-build ccache` (or equivalent for your distribution)

The build script will automatically use these tools if available, falling back to Make if Ninja is not found.

## Build Automation

### Release Builds
The release script (`mac/scripts/release.sh`) forces a fresh web/native build and requires the pinned custom Node.js runtime. It builds and caches the runtime when needed; releases fail instead of silently embedding the full system Node.js binary.

### Manual Custom Node.js Build

To build the custom Node.js manually (outside of Xcode):

```bash
cd web
node build-custom-node.js
```

This will:
1. Download the latest Node.js source
2. Configure it without unnecessary features
3. Build with optimizations (`-Os`, `-flto`, etc.)
4. Cache the result in `web/.node-builds/`

To use the custom Node.js for building the executable:

```bash
cd web
npm run build -- --custom-node
```

Or directly:

```bash
node build-native.js --custom-node
```

## Build Process Details

### Automatic Detection
The build system automatically searches for custom Node.js builds in `.node-builds/` when `--custom-node` is passed without a path. It finds the most recent build by checking directory modification times.

### Code Signing on macOS
When building the executable:
1. The Node.js binary is injected with our JavaScript code (SEA process)
2. The binary is stripped to remove debug symbols
3. The executable is re-signed with an ad-hoc signature

Note: You may see a warning about "invalidating the code signature" during the strip process - this is expected and harmless since we re-sign immediately after.

## Technical Details

### Features Disabled
- `--with-intl=small-icu` - Keeps Unicode semantics with reduced locale data
- `--without-npm` - Excludes npm from the binary
- `--without-corepack` - Removes package manager wrapper
- `--without-inspector` - Disables debugging protocol
- `--without-node-snapshot` - Skips V8 snapshot (~2-3MB)
- `--without-node-code-cache` - Skips code cache (~1-2MB)

### Optimization Flags
- `-Os` - Optimize for size
- `-flto` - Link-time optimization
- `-ffunction-sections` / `-fdata-sections` - Enable dead code elimination
- `-Wl,-dead_strip` - Remove unused code at link time

### Build Cache
Custom Node.js builds are stored in `web/.node-builds/` and are excluded from git via `.gitignore`. The build system automatically detects and reuses existing builds.

## File Locations

- Build script: `web/build-custom-node.js`
- Native executable builder: `web/build-native.js`
- Xcode integration: `mac/scripts/build-web-frontend.sh`
- Build output: `web/.node-builds/node-v*-minimal/`
- Final executable: `web/native/vibetunnel`

## Troubleshooting

### Custom Node.js not detected
- Ensure the build completed successfully: check for `.node-builds/node-v*-minimal/out/Release/node`
- In Debug mode, the system will use custom Node.js if already built
- In Release mode, it will build custom Node.js automatically if not present

### Code signature warnings
The warning "changes being made to the file will invalidate the code signature" is expected and handled automatically. The build process re-signs the executable after all modifications.

## Known Limitations

- The custom Node.js build process takes 10-20 minutes on first run
- Cross-compilation is not supported - you must build on the target platform
- The custom build excludes some features that may be needed by certain npm packages
- Native module compatibility issues may occur when mixing Node.js versions

---

### Deployment (`docs/deployment.md`)

# Deployment

VibeTunnel deployment encompasses macOS app distribution, automatic updates via Sparkle, and CLI tool installation. The release process is highly automated with comprehensive signing, notarization, and update feed generation.

## Package Types

**macOS Application Bundle** - Main VibeTunnel.app bundle with embedded resources (mac/build/Build/Products/Release/VibeTunnel.app)
- Signed with Developer ID Application certificate
- Notarized by Apple for Gatekeeper approval
- Contains embedded Bun server executable and CLI binaries

**DMG Distribution** - Disk image for user downloads (mac/build/VibeTunnel-{version}.dmg)
- Created by mac/scripts/create-dmg.sh
- Signed and notarized by mac/scripts/notarize-dmg.sh
- Contains app bundle and Applications symlink

**CLI Tools Package** - Command line binaries installed to /usr/local/bin
- vibetunnel binary (main CLI tool)
- vt wrapper script (convenience command)
- Installed via mac/VibeTunnel/Utilities/CLIInstaller.swift

## Platform Deployment

### Automated Release Process

**Complete Release Workflow** - mac/scripts/release.sh orchestrates the entire process:
```bash
./scripts/release.sh stable         # Stable release
./scripts/release.sh beta 2         # Beta release 2
./scripts/release.sh alpha 1        # Alpha release 1
```

**Pre-flight Checks** - mac/scripts/preflight-check.sh validates:
- Git repository state (clean working tree, on main branch)
- Build environment (Xcode, certificates, tools)
- Version configuration (mac/VibeTunnel/version.xcconfig)
- Notarization credentials (environment variables)

**Build and Signing** - mac/scripts/build.sh with mac/scripts/sign-and-notarize.sh:
- Builds ARM64-only binary (Apple Silicon)
- Signs with hardened runtime and entitlements
- Notarizes with Apple using API key authentication
- Staples notarization ticket to app bundle

### Code Signing Configuration

**Signing Script** - mac/scripts/codesign-app.sh handles deep signing:
- Signs all embedded frameworks and binaries
- Special handling for Sparkle XPC services (lines 89-145)
- Preserves existing signatures with timestamps
- Uses Developer ID Application certificate

**Notarization Process** - mac/scripts/notarize-app.sh submits to Apple:
- Creates secure timestamp signatures
- Submits via notarytool with API key (lines 38-72)
- Waits for Apple processing (timeout: 30 minutes)
- Staples ticket on success (lines 104-115)

### Sparkle Update System

**Update Configuration** - mac/VibeTunnel/Core/Services/SparkleUpdaterManager.swift:
- Automatic update checking enabled (line 78)
- Automatic downloads enabled (line 81)
- 24-hour check interval (line 84)
- Supports stable and pre-release channels (lines 152-160)

**Appcast Generation** - mac/scripts/generate-appcast.sh creates update feeds:
- Fetches releases from GitHub API (lines 334-338)
- Generates EdDSA signatures using private key (lines 95-130)
- Creates appcast.xml (stable only) and appcast-prerelease.xml
- Embeds changelog from local CHANGELOG.md (lines 259-300)

**Update Channels** - Configured in mac/VibeTunnel/Models/UpdateChannel.swift:
- Stable: https://vibetunnel.sh/appcast.xml
- Pre-release: https://vibetunnel.sh/appcast-prerelease.xml

### CLI Installation

**Installation Manager** - mac/VibeTunnel/Utilities/CLIInstaller.swift:
- Checks installation status (lines 41-123)
- Handles version updates (lines 276-341)
- Creates /usr/local/bin if needed (lines 407-411)
- Installs via osascript for sudo privileges (lines 470-484)

**Server Configuration** (lines 398-453):
- Bun server: Creates vt wrapper script that prepends 'fwd' command

### GitHub Release Creation

**Release Publishing** - Handled by mac/scripts/release.sh (lines 500-600):
- Creates and pushes git tags
- Uploads DMG to GitHub releases
- Generates release notes from CHANGELOG.md
- Marks pre-releases appropriately

**Release Verification** - Multiple verification steps:
- DMG signature verification (lines 429-458)
- App notarization check inside DMG (lines 462-498)
- Sparkle component timestamp signatures (lines 358-408)

## Reference

### Environment Variables
```bash
# Required for notarization
APP_STORE_CONNECT_API_KEY_P8    # App Store Connect API key content
APP_STORE_CONNECT_KEY_ID         # API Key ID
APP_STORE_CONNECT_ISSUER_ID      # API Issuer ID

# Optional
DMG_VOLUME_NAME                  # Custom DMG volume name
SIGN_IDENTITY                    # Override signing identity
```

### Key Scripts and Locations
- **Release orchestration**: mac/scripts/release.sh
- **Build configuration**: mac/scripts/build.sh, mac/scripts/common.sh
- **Signing pipeline**: mac/scripts/sign-and-notarize.sh, mac/scripts/codesign-app.sh
- **Notarization**: mac/scripts/notarize-app.sh, mac/scripts/notarize-dmg.sh
- **DMG creation**: mac/scripts/create-dmg.sh
- **Appcast generation**: mac/scripts/generate-appcast.sh
- **Version management**: mac/VibeTunnel/version.xcconfig
- **Sparkle private key**: mac/private/sparkle_private_key

### Release Artifacts
- **Application bundle**: mac/build/Build/Products/Release/VibeTunnel.app
- **Signed DMG**: mac/build/VibeTunnel-{version}.dmg
- **Update feeds**: appcast.xml, appcast-prerelease.xml (repository root)
- **GitHub releases**: https://github.com/amantus-ai/vibetunnel/releases

### Common Issues
- **Notarization failures**: Check API credentials, ensure valid Developer ID certificate
- **Sparkle signature errors**: Verify sparkle_private_key exists at mac/private/
- **Build number conflicts**: Increment CURRENT_PROJECT_VERSION in version.xcconfig
- **Double version suffixes**: Ensure version.xcconfig has correct format before release

---

### Development (`docs/development.md`)

# VibeTunnel Development Guide

## Overview

VibeTunnel follows modern Swift 6 and TypeScript development practices with a focus on async/await patterns, protocol-oriented design, and reactive UI architectures. The codebase is organized into three main components: macOS app (Swift/SwiftUI), iOS app (Swift/SwiftUI), and web dashboard (TypeScript/Lit).

Key architectural principles:
- **Protocol-oriented design** for flexibility and testability
- **Async/await** throughout for clean asynchronous code
- **Observable pattern** for reactive state management
- **Dependency injection** via environment values in SwiftUI

## Code Style

### Swift Conventions

**Modern Swift 6 patterns** - From `mac/VibeTunnel/Core/Services/ServerManager.swift`:
```swift
@MainActor
@Observable
class ServerManager {
    @MainActor static let shared = ServerManager()
    
    private(set) var serverType: ServerType = .bun
    private(set) var isSwitchingServer = false
    
    var port: String {
        get { UserDefaults.standard.string(forKey: "serverPort") ?? "4020" }
        set { UserDefaults.standard.set(newValue, forKey: "serverPort") }
    }
}
```

**Error handling** - From `mac/VibeTunnel/Core/Protocols/VibeTunnelServer.swift`:
```swift
enum ServerError: LocalizedError {
    case binaryNotFound(String)
    case startupFailed(String)
    case portInUse(Int)
    case invalidConfiguration(String)
    
    var errorDescription: String? {
        switch self {
        case .binaryNotFound(let binary):
            return "Server binary not found: \(binary)"
        case .startupFailed(let reason):
            return "Server failed to start: \(reason)"
        }
    }
}
```

**SwiftUI view patterns** - From `mac/VibeTunnel/Presentation/Views/Settings/GeneralSettingsView.swift`:
```swift
struct GeneralSettingsView: View {
    @AppStorage("autostart")
    private var autostart = false
    
    @State private var isCheckingForUpdates = false
    
    private let startupManager = StartupManager()
    
    var body: some View {
        NavigationStack {
            Form {
                Section {
                    VStack(alignment: .leading, spacing: 4) {
                        Toggle("Launch at Login", isOn: launchAtLoginBinding)
                        Text("Automatically start VibeTunnel when you log in.")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                    }
                }
            }
        }
    }
}
```

### TypeScript Conventions

**Class-based services** - From `web/src/server/services/ws-v3-hub.ts`:
```typescript
export class WsV3Hub {
  constructor(config: {
    ptyManager: PtyManager;
    terminalManager: TerminalManager;
    castOutputHub: CastOutputHub;
    gitStatusHub: GitStatusHub;
    remoteRegistry: RemoteRegistry | null;
    isHQMode: boolean;
  }) {}

  handleClientConnection(ws: WebSocket, req: IncomingMessage) {
    // subscribe/unsubscribe + fanout (stdout/snapshots/events)
  }
}
```

**Lit components** - From `web/src/client/components/vibe-terminal-buffer.ts`:
```typescript
@customElement('vibe-terminal-buffer')
export class VibeTerminalBuffer extends LitElement {
  // Disable shadow DOM for Tailwind compatibility
  createRenderRoot() {
    return this as unknown as HTMLElement;
  }
  
  @property({ type: String }) sessionId = '';
  @state() private buffer: BufferSnapshot | null = null;
  @state() private error: string | null = null;
}
```

## Common Patterns

### Service Architecture

**Protocol-based services** - Services define protocols for testability:
```swift
// mac/VibeTunnel/Core/Protocols/VibeTunnelServer.swift
@MainActor
protocol VibeTunnelServer: AnyObject {
    var isRunning: Bool { get }
    var port: String { get set }
    var logStream: AsyncStream<ServerLogEntry> { get }
    
    func start() async throws
    func stop() async
    func checkHealth() async -> Bool
}
```

**Singleton managers** - Core services use thread-safe singletons:
```swift
// mac/VibeTunnel/Core/Services/ServerManager.swift:14
@MainActor static let shared = ServerManager()

// ios/VibeTunnel/Services/APIClient.swift:93
static let shared = APIClient()
```

### Async/Await Patterns

**Swift async operations** - From `ios/VibeTunnel/Services/APIClient.swift`:
```swift
func getSessions() async throws -> [Session] {
    guard let url = makeURL(path: "/api/sessions") else {
        throw APIError.invalidURL
    }
    
    let (data, response) = try await session.data(from: url)
    
    guard let httpResponse = response as? HTTPURLResponse else {
        throw APIError.invalidResponse
    }
    
    if httpResponse.statusCode != 200 {
        throw APIError.serverError(httpResponse.statusCode, nil)
    }
    
    return try decoder.decode([Session].self, from: data)
}
```

**TypeScript async patterns** - See `web/src/server/services/ws-v3-hub.ts` (async frame handling + per-client subscription state).

### Error Handling

**Swift error enums** - Comprehensive error types with localized descriptions:
```swift
// ios/VibeTunnel/Services/APIClient.swift:4-70
enum APIError: LocalizedError {
    case invalidURL
    case serverError(Int, String?)
    case networkError(Error)
    
    var errorDescription: String? {
        switch self {
        case .serverError(let code, let message):
            if let message { return message }
            switch code {
            case 400: return "Bad request"
            case 401: return "Unauthorized"
            default: return "Server error: \(code)"
            }
        }
    }
}
```

**TypeScript error handling** - Structured error responses:
```typescript
// web/src/server/middleware/auth.ts
try {
  // Operation
} catch (error) {
  console.error('[Auth] Error:', error);
  res.status(500).json({ 
    error: 'Internal server error',
    message: error instanceof Error ? error.message : 'Unknown error'
  });
}
```

### State Management

**SwiftUI Observable** - From `mac/VibeTunnel/Core/Services/ServerManager.swift`:
```swift
@Observable
class ServerManager {
    private(set) var isRunning = false
    private(set) var isRestarting = false
    private(set) var lastError: Error?
}
```

**AppStorage for persistence**:
```swift
// mac/VibeTunnel/Presentation/Views/Settings/GeneralSettingsView.swift:5
@AppStorage("autostart") private var autostart = false
@AppStorage("updateChannel") private var updateChannelRaw = UpdateChannel.stable.rawValue
```

### UI Patterns

**SwiftUI form layouts** - From `mac/VibeTunnel/Presentation/Views/Settings/GeneralSettingsView.swift`:
```swift
Form {
    Section {
        VStack(alignment: .leading, spacing: 4) {
            Toggle("Launch at Login", isOn: launchAtLoginBinding)
            Text("Description")
                .font(.caption)
                .foregroundStyle(.secondary)
        }
    } header: {
        Text("Application")
            .font(.headline)
    }
}
.formStyle(.grouped)
```

**Lit reactive properties**:
```typescript
// web/src/client/components/vibe-terminal-buffer.ts:22-24
@property({ type: String }) sessionId = '';
@state() private buffer: BufferSnapshot | null = null;
@state() private error: string | null = null;
```

## Workflows

### Adding a New Service

1. **Define the protocol** in `mac/VibeTunnel/Core/Protocols/`:
```swift
@MainActor
protocol MyServiceProtocol {
    func performAction() async throws
}
```

2. **Implement the service** in `mac/VibeTunnel/Core/Services/`:
```swift
@MainActor
class MyService: MyServiceProtocol {
    static let shared = MyService()
    
    func performAction() async throws {
        // Implementation
    }
}
```

3. **Add to environment** if needed in `mac/VibeTunnel/Core/Extensions/EnvironmentValues+Services.swift`

### Creating UI Components

**SwiftUI views** follow this pattern:
```swift
struct MyView: View {
    @Environment(\.myService) private var service
    @State private var isLoading = false
    
    var body: some View {
        // View implementation
    }
}
```

**Lit components** use decorators:
```typescript
@customElement('my-component')
export class MyComponent extends LitElement {
    @property({ type: String }) value = '';
    
    render() {
        return html`<div>${this.value}</div>`;
    }
}
```

### Testing Patterns

**Swift unit tests** - From `mac/VibeTunnelTests/ServerManagerTests.swift`:
```swift
@MainActor
final class ServerManagerTests: XCTestCase {
    override func setUp() async throws {
        await super.setUp()
        // Setup
    }
    
    func testServerStart() async throws {
        let manager = ServerManager.shared
        await manager.start()
        XCTAssertTrue(manager.isRunning)
    }
}
```

**TypeScript tests** use Vitest:
```typescript
// web/src/test/setup.ts
import { describe, it, expect } from 'vitest';

describe('WsV3Hub', () => {
  it('should handle client connections', async () => {
    // Test implementation
  });
});
```

## Reference

### File Organization

**Swift packages**:
- `mac/VibeTunnel/Core/` - Core business logic, protocols, services
- `mac/VibeTunnel/Presentation/` - SwiftUI views and view models
- `mac/VibeTunnel/Utilities/` - Helper classes and extensions
- `ios/VibeTunnel/Services/` - iOS-specific services
- `ios/VibeTunnel/Views/` - iOS UI components

**TypeScript modules**:
- `web/src/client/` - Frontend components and utilities
- `web/src/server/` - Backend services and routes
- `web/src/server/pty/` - Terminal handling
- `web/src/test/` - Test files and utilities

### Naming Conventions

**Swift**:
- Services: `*Manager`, `*Service` (e.g., `ServerManager`, `APIClient`)
- Protocols: `*Protocol`, `*able` (e.g., `VibeTunnelServer`, `HTTPClientProtocol`)
- Views: `*View` (e.g., `GeneralSettingsView`, `TerminalView`)
- Errors: `*Error` enum (e.g., `ServerError`, `APIError`)

**TypeScript**:
- Services: `*Service`, `*Manager`, `*Hub` (e.g., `WsV3Hub`, `TerminalManager`, `CastOutputHub`)
- Components: `vibe-*` custom elements (e.g., `vibe-terminal-buffer`)
- Types: PascalCase interfaces (e.g., `BufferSnapshot`, `ServerConfig`)

### Common Issues

**Port conflicts** - Handled in `mac/VibeTunnel/Core/Utilities/PortConflictResolver.swift`
**Permission management** - See `mac/VibeTunnel/Core/Services/*PermissionManager.swift`
**WebSocket reconnection** - Implemented in `ios/VibeTunnel/Services/BufferWebSocketClient.swift`
**Terminal resizing** - Handled in both Swift and TypeScript terminal components

### VibeTunnel CLI Wrapper (vt)

The `vt` command is a bash wrapper script that allows users to run commands through VibeTunnel's terminal forwarding. It's installed at `/usr/local/bin/vt` when the Mac app is built.

**Source location**: `mac/VibeTunnel/vt`

**Usage**:
```bash
# Run a command through VibeTunnel
vt ls -la

# Run an aliased command (e.g., if 'claude' is an alias)
vt claude --version

# Launch interactive shell
vt --shell
vt -i

# Run command without shell wrapping (bypass alias resolution)
vt --no-shell-wrap command
vt -S command
```

**How it works**:
1. Locates the VibeTunnel.app bundle (checks standard locations and uses Spotlight if needed)
2. Finds the `vibetunnel` binary within the app bundle's Resources
3. Determines if the command is a binary or alias/function
4. For binaries: executes directly through `vibetunnel fwd`
5. For aliases/functions: wraps in appropriate shell (`zsh -i -c` or `bash -c`) for proper resolution

**Technical Details**:
- The `--` separator should not be passed to `fwd` as it was being misinterpreted as a command
- Aliases require interactive shell mode to be resolved properly
- The script prevents recursive VibeTunnel sessions by checking `VIBETUNNEL_SESSION_ID`
- The `fwd` binary now properly handles `--` as an argument separator when needed

## Web Development

### Code Quality Tools

VibeTunnel uses several tools to maintain code quality:

#### Running All Checks

To run all code quality checks (read-only checks run in parallel):

```bash
pnpm run check
```

This runs format checking, linting, and type checking in parallel and reports any issues.

#### Individual Tools

**Formatting** (Biome):
```bash
pnpm run format        # Fix formatting issues
pnpm run format:check  # Check formatting without fixing
```

**Linting** (Biome + TypeScript):
```bash
pnpm run lint      # Check for lint errors
pnpm run lint:fix  # Fix auto-fixable lint errors
```

**Type Checking** (TypeScript):
```bash
pnpm run typecheck  # Run type checking on all configs
```

#### Auto-fix All Issues

To automatically fix all formatting and linting issues:

```bash
pnpm run check:fix
```

This runs format and lint:fix **sequentially** to avoid file conflicts.

### Why Sequential Fixes?

Running multiple file-modifying tools in parallel can cause race conditions where:
- Both tools try to write to the same file simultaneously
- One tool's changes get overwritten by another
- Git operations fail due to file locks

Best practices from the JavaScript community recommend:
1. **Parallel for checks**: Read-only operations can run simultaneously
2. **Sequential for fixes**: File modifications should happen one after another
3. **Biome as unified tool**: Reduces conflicts by combining formatting and linting

### Why Multiple Tools?

1. **Biome**: Fast, modern formatter and linter for JavaScript/TypeScript
2. **TypeScript**: Type checking across server, client, and service worker contexts
3. **Parallel execution**: Saves time by running independent checks simultaneously

### Tips for Faster Development

1. **Use `pnpm run check` before committing** - Catches all issues at once
2. **Enable format-on-save in your editor** - Prevents formatting issues
3. **Run `pnpm run check:fix` to quickly fix issues** - Handles problems sequentially

### Continuous Development

When developing, you typically want:

```bash
# Terminal 1: Run the dev server
pnpm run dev

# Terminal 2: Run tests in watch mode (when needed)
pnpm test

# Before committing: Run all checks
pnpm run check
```

---

### Files (`docs/files.md`)

# VibeTunnel Files Catalog

## Overview

VibeTunnel is a cross-platform terminal sharing application organized into distinct platform modules: macOS native app, iOS companion app, and a TypeScript web server. The codebase follows a clear separation of concerns with platform-specific implementations sharing common protocols and interfaces.

The project structure emphasizes modularity with separate build systems for each platform - Xcode projects for Apple platforms and Node.js/TypeScript tooling for the web server. Configuration is managed through xcconfig files, Package.swift manifests, and package.json files.

## Core Source Files

### macOS Application (mac/)

**Main Entry Points**
- `VibeTunnel/VibeTunnelApp.swift` - macOS app entry point with lifecycle management
- `VibeTunnel/Core/Protocols/VibeTunnelServer.swift` - Server protocol definition
- `VibeTunnel/Core/Services/ServerManager.swift` - Central server orchestration

**Core Services**
- `VibeTunnel/Core/Services/BunServer.swift` - Bun runtime server implementation
- `VibeTunnel/Core/Services/BaseProcessServer.swift` - Base server process management
- `VibeTunnel/Core/Services/TTYForwardManager.swift` - Terminal forwarding coordinator
- `VibeTunnel/Core/Services/TerminalManager.swift` - Terminal app integration
- `VibeTunnel/Core/Services/SessionMonitor.swift` - Session lifecycle tracking
- `VibeTunnel/Core/Services/NgrokService.swift` - Tunnel service integration
- `VibeTunnel/Core/Services/WindowTracker.swift` - Window state management

**Security & Permissions**
- `VibeTunnel/Core/Services/DashboardKeychain.swift` - Secure credential storage
- `VibeTunnel/Core/Services/AccessibilityPermissionManager.swift` - Accessibility permissions
- `VibeTunnel/Core/Services/ScreenRecordingPermissionManager.swift` - Screen recording permissions
- `VibeTunnel/Core/Services/AppleScriptPermissionManager.swift` - AppleScript permissions

**UI Components**
- `VibeTunnel/Presentation/Views/MenuBarView.swift` - Menu bar interface
- `VibeTunnel/Presentation/Views/WelcomeView.swift` - Onboarding flow
- `VibeTunnel/Presentation/Views/SettingsView.swift` - Settings window
- `VibeTunnel/Presentation/Views/SessionDetailView.swift` - Session detail view

### iOS Application (ios/)

**Main Entry Points**
- `VibeTunnel/App/VibeTunnelApp.swift` - iOS app entry point
- `VibeTunnel/App/ContentView.swift` - Root content view

**Services**
- `VibeTunnel/Services/APIClient.swift` - HTTP API client
- `VibeTunnel/Services/BufferWebSocketClient.swift` - WebSocket terminal client
- `VibeTunnel/Services/SessionService.swift` - Session management
- `VibeTunnel/Services/NetworkMonitor.swift` - Network connectivity

**Terminal Views**
- `VibeTunnel/Views/Terminal/TerminalView.swift` - Main terminal view
- `VibeTunnel/Views/Terminal/GhosttyWebView.swift` - Ghostty terminal renderer
- `VibeTunnel/Views/Terminal/TerminalBufferRenderer.swift` - Buffer snapshot → ANSI conversion
- `VibeTunnel/Views/Terminal/TerminalToolbar.swift` - Terminal controls
- `VibeTunnel/Views/Terminal/CastPlayerView.swift` - Recording playback

**Data Models**
- `VibeTunnel/Models/Session.swift` - Terminal session model
- `VibeTunnel/Models/TerminalData.swift` - Terminal buffer data
- `VibeTunnel/Models/ServerConfig.swift` - Server configuration

### Web Server (web/)

**Server Entry Points**
- `src/index.ts` - Main server entry
- `src/server/server.ts` - Express server setup
- `src/server/app.ts` - Application configuration

**Terminal Management**
- `src/server/pty/pty-manager.ts` - PTY process management
- `src/server/pty/session-manager.ts` - Session lifecycle
- `src/server/services/terminal-manager.ts` - Terminal service layer
- `src/server/services/ws-v3-hub.ts` - WebSocket v3 hub (stdout/snapshots/input)
- `src/server/services/cast-output-hub.ts` - Asciicast tail + pruning for stdout

**API Routes**
- `src/server/routes/sessions.ts` - Session API endpoints
- `src/server/routes/remotes.ts` - Remote connection endpoints

**Client Application**
- `src/client/app-entry.ts` - Web client entry
- `src/client/app.ts` - Main application logic
- `src/client/components/terminal.ts` - Web terminal component
- `src/client/components/vibe-terminal-buffer.ts` - Buffer terminal component
- `src/client/services/terminal-socket-client.ts` - WebSocket v3 transport (single socket)

## Platform Implementation

### macOS Platform Files
- `apple/Local.xcconfig` - Local build configuration
- `mac/VibeTunnel/Shared.xcconfig` - Shared build settings
- `mac/VibeTunnel/version.xcconfig` - Version configuration
- `mac/VibeTunnel.entitlements` - App entitlements
- `mac/VibeTunnel-Info.plist` - App metadata

### iOS Platform Files
- `ios/Package.swift` - Swift package manifest
- `ios/project.yml` - XcodeGen configuration
- `ios/VibeTunnel/Resources/Info.plist` - iOS app metadata

### Web Platform Files
- `web/package.json` - Node.js dependencies
- `web/tsconfig.json` - TypeScript configuration
- `web/vite.config.ts` - Vite build configuration
- `web/tailwind.config.js` - Tailwind CSS configuration

## Build System

### macOS Build Scripts
- `mac/scripts/build.sh` - Main build script
- `mac/scripts/build-bun-executable.sh` - Bun server build
- `mac/scripts/copy-bun-executable.sh` - Resource copying
- `mac/scripts/codesign-app.sh` - Code signing
- `mac/scripts/notarize-app.sh` - App notarization
- `mac/scripts/create-dmg.sh` - DMG creation
- `mac/scripts/release.sh` - Release automation

### Web Build Scripts
- `web/scripts/clean.js` - Build cleanup
- `web/scripts/copy-assets.js` - Asset management
- `web/scripts/ensure-dirs.js` - Directory setup
- `web/build-native.js` - Native binary builder

### Configuration Files
- `mac/VibeTunnel.xcodeproj/project.pbxproj` - Xcode project
- `ios/VibeTunnel.xcodeproj/project.pbxproj` - iOS Xcode project
- `web/eslint.config.js` - ESLint configuration
- `web/vitest.config.ts` - Test configuration

## Configuration

### App Configuration
- `mac/VibeTunnel/Core/Models/AppConstants.swift` - App constants
- `mac/VibeTunnel/Core/Models/UpdateChannel.swift` - Update channels
- `ios/VibeTunnel/Models/ServerConfig.swift` - Server settings

### Assets & Resources
- `assets/AppIcon.icon/` - App icon assets
- `mac/VibeTunnel/Assets.xcassets/` - macOS asset catalog
- `ios/VibeTunnel/Resources/Assets.xcassets/` - iOS asset catalog
- `web/public/` - Web static assets

### Documentation
- `docs/API.md` - API documentation
- `docs/ARCHITECTURE.md` - Architecture overview
- `mac/Documentation/BunServerSupport.md` - Bun server documentation
- `web/src/server/pty/README.md` - PTY implementation notes

## Reference

### File Organization Patterns
- Platform code separated by directory: `mac/`, `ios/`, `web/`
- Swift code follows MVC-like pattern: Models, Views, Services
- TypeScript organized by client/server with feature-based subdirectories
- Build scripts consolidated in platform-specific `scripts/` directories

### Naming Conventions
- Swift files: PascalCase matching class/struct names
- TypeScript files: kebab-case for modules, PascalCase for classes
- Configuration files: lowercase with appropriate extensions
- Scripts: kebab-case shell scripts

### Key Dependencies
- macOS: SwiftUI, Sparkle (updates), Bun runtime
- iOS: SwiftUI, ghostty-web resources, WebSocket client
- Web: Express, ghostty-web, WebSocket, Vite bundler

---

### Forwarder (`docs/forwarder.md`)

# Native Forwarder

`vibetunnel-fwd` is VibeTunnel's per-session native process. The `vt` wrapper launches it around a command; the web server watches the resulting session artifacts and sends control messages over a Unix socket.

```text
vt -> vibetunnel-fwd -> PTY -> child command
                    -> session files and ipc.sock <- web server
```

The implementation is a Rust crate in `native/vt-fwd`. It supports macOS and Linux; Windows is not supported.

## Session Contract

Each session uses `~/.vibetunnel/control/<session-id>/` by default. `VIBETUNNEL_CONTROL_DIR` overrides the control root.

- `session.json` contains the current `SessionInfo` state.
- `stdout` is an asciinema v2 stream with output, input, resize, and exit records.
- `stdin` is a private FIFO kept for compatibility.
- `ipc.sock` accepts live input and control commands and is removed when the forwarder exits.

The session JSON schema is defined in `web/src/shared/types.ts`. The forwarder creates private directories and artifacts so session contents are not readable by other local users.

## IPC Contract

Frames use one byte for the message type, a four-byte big-endian payload length, and the payload. `STDIN_DATA` payloads are raw bytes; `CONTROL_CMD` payloads are JSON. The command set includes resize, kill, reset-size, and update-title. Heartbeat frames are echoed.

The server-side framing helpers are in `web/src/server/pty/socket-protocol.ts`. The forwarder independently enforces a 1 MiB inbound payload limit in `native/vt-fwd/src/control_socket.rs`; an oversized frame closes only the offending connection. Unknown message types are ignored.

## CLI Contract

The forwarder accepts a command after its options:

```text
vibetunnel-fwd [--session-id <id>] [--title-mode <mode>] [--verbosity <level>] <command> [args...]
```

`--update-title` updates an existing session and exits. Title modes are `none`, `filter`, and `static`. `VIBETUNNEL_TITLE_MODE`, `VIBETUNNEL_LOG_LEVEL`, `VIBETUNNEL_DEBUG`, and `VIBETUNNEL_CONTROL_DIR` are accepted as inherited overrides; explicit CLI options take precedence. The `vt` and web wrappers normally pass title mode and verbosity as CLI options. For the child command, the forwarder exports the active `VIBETUNNEL_SESSION_ID` and `TERM=xterm-256color`.

## Build and Test

Install rustup; `native/vt-fwd/rust-toolchain.toml` selects Rust 1.97.0 and the required rustfmt and Clippy components. `native/vt-fwd/Cargo.lock` pins crate dependencies.

```bash
cd native/vt-fwd
cargo fmt --all -- --check
cargo clippy --all-targets --all-features --locked -- -D warnings
cargo test --all-targets --locked
cargo build --release --locked
python3 test/e2e.py target/release/vibetunnel-fwd
```

The release binary is `native/vt-fwd/target/release/vibetunnel-fwd`. `node web/scripts/build-fwd-rust.js` builds and installs the host binary for web and macOS packaging; the npm build creates platform-specific forwarder directories.

---

### Gemini (`docs/gemini.md`)

# Using Gemini CLI for Large Codebase Analysis

When analyzing large codebases or multiple files that might exceed context limits, use the Gemini CLI with its massive context window (up to 2 million tokens). Use `gemini -p` to leverage Google Gemini's large context capacity.

## Prerequisites

Ensure Gemini CLI is installed before using these commands. Installation instructions: https://github.com/google/gemini-cli#installation

## File and Directory Inclusion Syntax

Use the `@` syntax to include files and directories in your Gemini prompts. The paths should be relative to WHERE you run the gemini command:

### Examples:

**Single file analysis:**
```bash
gemini -p "@web/src/server/server.ts Explain this file's purpose and structure"
```

**Multiple files:**
```bash
gemini -p "@package.json @web/src/client/app.ts Analyze the dependencies used in the code"
```

**Entire directory:**
```bash
gemini -p "@src/ Summarize the architecture of this codebase"
```

**Multiple directories:**
```bash
gemini -p "@src/ @tests/ Analyze test coverage for the source code"
```

**Current directory and subdirectories:**
```bash
gemini -p "@./ Give me an overview of this entire project"
# Or use --all_files flag:
gemini --all_files -p "Analyze the project structure and dependencies"
```

**⚠️ Security Warning:** Using `@./` may include sensitive files like `.env`, `.git/`, or API keys. Consider using specific directories or exclusion patterns.

## Implementation Verification Examples

**Check if a feature is implemented:**
```bash
gemini -p "@src/ @lib/ Has dark mode been implemented in this codebase? Show me the relevant files and functions"
```

**Verify authentication implementation:**
```bash
gemini -p "@src/ @middleware/ Is JWT authentication implemented? List all auth-related endpoints and middleware"
```

**Check for specific patterns:**
```bash
gemini -p "@src/ Are there any React hooks that handle WebSocket connections? List them with file paths"
```

**Verify error handling:**
```bash
gemini -p "@src/ @api/ Is proper error handling implemented for all API endpoints? Show examples of try-catch blocks"
```

**Check for rate limiting:**
```bash
gemini -p "@backend/ @middleware/ Is rate limiting implemented for the API? Show the implementation details"
```

**Verify caching strategy:**
```bash
gemini -p "@src/ @lib/ @services/ Is Redis caching implemented? List all cache-related functions and their usage"
```

**Check for specific security measures:**
```bash
gemini -p "@src/ @api/ Are SQL injection protections implemented? Show how user inputs are sanitized"
```

**Verify test coverage for features:**
```bash
gemini -p "@src/payment/ @tests/ Is the payment processing module fully tested? List all test cases"
```

## Error Scenarios and Handling

**Gemini CLI not installed:**
```bash
# Error: command not found: gemini
# Solution: Install Gemini CLI following instructions at https://github.com/google/gemini-cli#installation
```

**Invalid file paths:**
```bash
gemini -p "@nonexistent/path/ Analyze this code"
# Gemini will report: "Error: Path 'nonexistent/path/' does not exist"
# The command continues with any valid paths provided
```

**Network connectivity issues:**
```bash
# If API calls fail, you'll see:
# "Error: Failed to connect to Gemini API"
# Solution: Check your internet connection and API key configuration
```

**File encoding problems:**
```bash
# Non-UTF8 files are automatically skipped with a warning:
# "Warning: Skipping binary or non-UTF8 file: path/to/file"
```

**Context limit exceeded:**
```bash
# For extremely large codebases:
# "Error: Context limit exceeded (2M tokens)"
# Solution: Use more specific paths or directories instead of @./
```

**Timeout during analysis:**
```bash
# Default timeout is 30 seconds for API calls
# For longer operations, use the --timeout flag:
gemini --timeout 120 -p "@large-codebase/ Analyze architecture"
```

## When to Use Gemini CLI

Use `gemini -p` when:
- Analyzing entire codebases or large directories
- Comparing multiple large files
- Need to understand project-wide patterns or architecture
- Current context window is insufficient for the task
- Working with files totaling more than 100KB
- Verifying if specific features, patterns, or security measures are implemented
- Checking for the presence of certain coding patterns across the entire codebase

## Important Notes

- Paths in @ syntax are relative to your current working directory when invoking gemini
- The CLI will include file contents directly in the context
- The --yolo flag bypasses confirmation prompts (use cautiously)
- Gemini's context window can handle entire codebases that would overflow Claude's context
- When checking implementations, be specific about what you're looking for to get accurate results
- Symlinks are followed by default - be cautious with circular references
- Binary files are automatically excluded from analysis
- Use `.geminiignore` file to exclude specific patterns (similar to `.gitignore`)

## Using .geminiignore

Create a `.geminiignore` file in your project root to exclude files and directories from Gemini analysis. The syntax follows `.gitignore` patterns:

```
# Environment and secrets
.env*
*.key
*.pem
secrets/
credentials/

# Build artifacts
dist/
build/
*.min.js
*.bundle.js

# Dependencies
node_modules/
vendor/
venv/

# Large generated files
package-lock.json
yarn.lock
*.generated.ts

# Test fixtures
test/fixtures/large-data/
**/*.snapshot.js

# IDE and OS files
.idea/
.vscode/
.DS_Store
```

**Pattern Examples:**
- `*.log` - Exclude all log files
- `temp/` - Exclude temp directory
- `!important.log` - Include important.log even if *.log is excluded
- `src/**/test.js` - Exclude test.js in any subdirectory of src
- `/config.json` - Exclude config.json only in root directory

## Error Handling

- If paths don't exist, Gemini will report an error and continue with valid paths
- File encoding issues are handled gracefully (non-UTF8 files are skipped)
- For large repositories, consider using specific subdirectories to avoid timeouts

---

### Git Hooks (`docs/git-hooks.md`)

# Git Hooks in VibeTunnel

## Overview

VibeTunnel uses Git hooks exclusively for its **follow mode** feature. These hooks monitor repository changes and enable automatic branch synchronization across team members.

## Purpose

Git hooks in VibeTunnel serve a single, specific purpose:
- **Follow Mode**: Automatically sync worktrees when team members switch branches
- **Session Title Updates**: Display current git operations in terminal session titles

**Important**: If you're not using follow mode, git hooks are not needed and serve no other purpose in VibeTunnel.

## How It Works

### Hook Installation

When follow mode is enabled, VibeTunnel installs two Git hooks:
- `post-commit`: Triggered after commits
- `post-checkout`: Triggered after branch checkouts

These hooks execute a simple command:
```bash
vt git event
```

### Event Flow

1. **Git Operation**: User performs a commit or checkout
2. **Hook Trigger**: Git executes the VibeTunnel hook
3. **Event Notification**: `vt git event` sends repository path to VibeTunnel server
4. **Server Processing**: The `/api/git/event` endpoint:
   - Updates session titles (e.g., `Terminal [checkout: feature-branch]`)
   - Checks follow mode configuration
   - Syncs branches if follow mode is active

### Follow Mode Synchronization

When follow mode is enabled for a branch:
1. VibeTunnel monitors checkouts to the followed branch
2. If detected, it automatically switches your worktree to that branch
3. If branches have diverged, follow mode is automatically disabled

## Technical Implementation

### Hook Script Content

```bash
#!/bin/sh
# VibeTunnel Git hook - post-checkout
# This hook notifies VibeTunnel when Git events occur

# Check if vt command is available
if command -v vt >/dev/null 2>&1; then
  # Run in background to avoid blocking Git operations
  vt git event &
fi

# Always exit successfully
exit 0
```

### Hook Management

- **Installation**: `installGitHooks()` in `web/src/server/utils/git-hooks.ts`
- **Safe Chaining**: Existing hooks are backed up and chained
- **Cleanup**: Original hooks are restored when uninstalling

### API Endpoints

- `POST /api/git/event`: Receives git event notifications
- `POST /api/worktrees/follow`: Enables follow mode and installs hooks
- `GET /api/git/follow`: Checks follow mode status

## File Locations

- **Hook Management**: `web/src/server/utils/git-hooks.ts`
- **Event Handler**: `web/src/server/routes/git.ts` (lines 189-481)
- **Follow Mode**: `web/src/server/routes/worktrees.ts` (lines 580-630)
- **CLI Integration**: `web/bin/vt` (git event command)

## Configuration

Follow mode stores configuration in git config:
```bash
git config vibetunnel.followBranch <branch-name>
```

## Security Considerations

- Hooks run with minimal permissions
- Commands execute in background to avoid blocking Git
- Existing hooks are preserved and chained safely
- Hooks are repository-specific, not global

## Troubleshooting

### Hooks Not Working
- Verify `vt` command is in PATH
- Check hook permissions: `ls -la .git/hooks/post-*`
- Ensure hooks are executable: `chmod +x .git/hooks/post-*`

### Follow Mode Issues
- Check configuration: `git config vibetunnel.followBranch`
- Verify hooks installed: `cat .git/hooks/post-checkout`
- Review server logs for git event processing

## Summary

Git hooks in VibeTunnel are:
- **Single-purpose**: Only used for follow mode functionality
- **Optional**: Not required unless using follow mode
- **Safe**: Preserve existing hooks and run non-blocking
- **Automatic**: Managed by VibeTunnel when enabling/disabling follow mode

If you're not using follow mode for team branch synchronization, you don't need git hooks installed.

---

### Git Worktree Follow Mode (`docs/git-worktree-follow-mode.md`)

# Git Worktree Follow Mode Specification

## Overview

Follow mode is a feature that enables automatic synchronization between Git worktrees and the main repository. It ensures team members stay on the same branch by automatically switching branches when changes are detected.

## Core Concept

Follow mode creates a **unidirectional sync** from a worktree to the main repository:
- When someone switches branches in a worktree
- The main repository automatically follows that branch change
- This keeps the main repository synchronized with active development

## When Follow Mode Should Be Available

### ✅ Follow Mode SHOULD appear when:

1. **Creating a session in a worktree**
   - You've selected a worktree from the dropdown
   - The session will run in that worktree's directory
   - Follow mode will sync the main repository to match this worktree's branch

2. **Viewing worktrees in the Worktree Manager**
   - Each worktree (except main) shows a "Follow" button
   - Enables following that specific worktree's branch

3. **Session list with worktree sessions**
   - Repository headers show follow mode status
   - Dropdown allows changing which worktree to follow

### ❌ Follow Mode should NOT appear when:

1. **No worktree is selected** (using main repository)
   - There's nothing to follow - you're already in the main repo
   - Follow mode has no purpose without a worktree

2. **Repository has no worktrees**
   - No worktrees exist to follow
   - Only the main repository is available

3. **Not in a Git repository**
   - Obviously, no Git features available

## UI Behavior Rules

### Session Creation Form

```typescript
// Show follow mode toggle only when:
const showFollowModeToggle = 
  gitRepoInfo?.isGitRepo && 
  selectedWorktree !== undefined &&
  selectedWorktree !== 'none';
```

#### Toggle States:

1. **Worktree Selected**:
   - Show: "Follow Mode" toggle
   - Description: "Keep main repository in sync with this worktree"
   - Default: OFF (user must explicitly enable)

2. **No Worktree Selected**:
   - Hide the entire follow mode section
   - No toggle should be visible

3. **Follow Mode Already Active**:
   - Show: "Follow Mode" toggle (disabled)
   - Description: "Currently following: [branch-name]"
   - Info: User must disable from worktree manager

### Worktree Manager

Each worktree row shows:
- **"Follow" button**: When not currently following
- **"Following" button** (green): When actively following this worktree
- **No button**: For the main worktree (can't follow itself)

### Session List

Repository headers show:
- **Purple badge**: When follow mode is active, shows branch name
- **Dropdown**: To change follow mode settings per repository

## Technical Implementation

### State Logic

```typescript
// Follow mode is only meaningful when:
// 1. We have a worktree to follow
// 2. We're not already in that worktree
// 3. The main repo can switch to that branch

const canEnableFollowMode = (
  worktree: Worktree,
  currentLocation: string,
  mainRepoPath: string
) => {
  // Can't follow if we're in the main repo with no worktree selected
  if (currentLocation === mainRepoPath && !worktree) {
    return false;
  }
  
  // Can't follow the main worktree
  if (worktree.isMainWorktree) {
    return false;
  }
  
  // Can follow if we're creating a session in a worktree
  if (worktree && currentLocation === worktree.path) {
    return true;
  }
  
  return false;
};
```

### Configuration Storage

Follow mode state is stored in Git config:
```bash
# Enable follow mode for a branch
git config vibetunnel.followBranch "feature/new-ui"

# Check current follow mode
git config vibetunnel.followBranch

# Disable follow mode
git config --unset vibetunnel.followBranch
```

### Synchronization Rules

1. **Automatic Sync**:
   - Triggered by `post-checkout` git hook in worktrees
   - Only syncs if main repo has no uncommitted changes
   - Disables follow mode if branches have diverged

2. **Manual Override**:
   - Users can always manually switch branches
   - Follow mode doesn't prevent manual git operations
   - Re-enables when returning to the followed branch

## User Experience Guidelines

### Clear Messaging

1. **When Enabling**:
   - "Follow mode will keep your main repository on the same branch as this worktree"
   - "Enable to automatically sync branch changes"

2. **When Active**:
   - "Following worktree: feature/new-ui"
   - "Main repository syncs with this worktree's branch"

3. **When Disabled**:
   - "Follow mode disabled due to uncommitted changes"
   - "Branches have diverged - follow mode disabled"

### Visual Indicators

- **Toggle Switch**: Only visible when applicable
- **Status Badge**: Purple badge with branch name when active
- **Button States**: Clear "Follow"/"Following" states in worktree manager

## Error Handling

### Common Scenarios

1. **Uncommitted Changes**:
   - Disable follow mode automatically
   - Show notification to user
   - Don't lose any work

2. **Branch Divergence**:
   - Detect when branches have different commits
   - Disable follow mode to prevent conflicts
   - Notify user of the situation

3. **Worktree Deletion**:
   - Automatically disable follow mode
   - Clean up git config
   - Update UI immediately

## Summary

Follow mode should be:
- **Contextual**: Only shown when it makes sense
- **Safe**: Never causes data loss or conflicts
- **Clear**: Users understand what it does
- **Automatic**: Works in the background when enabled

The key principle: **Follow mode only exists when there's a worktree to follow**. Without a worktree selection, the feature should not be visible or accessible.

---

### Hq (`docs/hq.md`)

# HQ Mode Documentation

HQ (Headquarters) mode allows multiple VibeTunnel servers to work together in a distributed setup, where one server acts as the central HQ and others register as remote servers.

## Overview

In HQ mode:
- **HQ Server**: Acts as a central aggregator and router
- **Remote Servers**: Individual VibeTunnel servers that register with the HQ
- **Clients**: Connect to the HQ server and can create/manage sessions on any remote

## How It Works

### 1. Registration Flow

When a remote server starts with HQ configuration:
1. It generates a unique bearer token
2. Registers itself with the HQ using Basic Auth (HQ credentials)
3. Provides its ID, name, URL, and bearer token
4. HQ stores this information in its `RemoteRegistry`

### 2. Session Management

**Creating Sessions:**
- Clients must specify a `remoteId` when creating sessions through HQ
- HQ forwards the request to the specified remote using the bearer token
- The remote creates the session locally
- HQ tracks which sessions belong to which remote

The web New Session form lists registered machines and requires one as the target. Working-directory paths are interpreted on that selected machine. Because repository discovery, directory browsing, and Git worktree helpers are local filesystem operations, the form hides those HQ-router helpers in distributed mode; enter the machine's path directly.

**Session Operations:**
- All session operations (get info, send input, kill, etc.) are proxied through HQ
- HQ checks its registry to find which remote owns the session
- Requests are forwarded with bearer token authentication

### 3. Health Monitoring

- HQ performs health checks every 15 seconds on all registered remotes
- Health check: `GET /api/health` with 5-second timeout
- Failed remotes are automatically unregistered
- Remotes refresh their registration every 60 seconds and automatically rejoin after sleep or a network interruption

### 4. Session Discovery

- Remote servers watch their control directory for new sessions
- When sessions are created/deleted, remotes notify HQ via `/api/remotes/{name}/refresh-sessions`
- HQ fetches the latest session list from the remote and updates its registry

## Setup

### Running an HQ Server

```bash
# Basic HQ server
vibetunnel-server --hq --username admin --password secret

# HQ server on custom port
vibetunnel-server --hq --port 8080 --username admin --password secret
```

### Running Remote Servers

```bash
# Remote server registering with HQ
vibetunnel-server \
  --username local-user \
  --password local-pass \
  --hq-url https://hq.example.com \
  --hq-username admin \
  --hq-password secret \
  --name production-1

# For local development (allow HTTP)
vibetunnel-server \
  --hq-url http://localhost:4020 \
  --hq-username admin \
  --hq-password secret \
  --name dev-remote \
  --allow-insecure-hq

# Advertise an address that the HQ can resolve and reach
vibetunnel-server \
  --hq-url https://hq.example.com \
  --hq-username admin \
  --hq-password secret \
  --name tailnet-remote \
  --remote-url http://100.64.0.5:4020
```

### Command-Line Options

**HQ Server Options:**
- `--hq` - Enable HQ mode
- `--username` - Admin username for HQ access
- `--password` - Admin password for HQ access

**Remote Server Options:**
- `--hq-url` - URL of the HQ server
- `--hq-username` - Username to authenticate with HQ
- `--hq-password` - Password to authenticate with HQ
- `--name` - Unique name for this remote server
- `--remote-url` - HTTP(S) origin the HQ uses to connect back to this remote; defaults to the bind address or hostname
- `--allow-insecure-hq` - Allow HTTP connections to HQ (dev only)
- `--no-hq-auth` - Disable HQ authentication (testing only)

## API Endpoints

### HQ-Specific Endpoints

**List Remotes:**
```http
GET /api/remotes
Authorization: Basic <base64(username:password)>

Response:
[
  {
    "id": "uuid",
    "name": "production-1",
    "url": "http://remote1:4020",
    "registeredAt": "2025-01-17T10:00:00.000Z",
    "lastHeartbeat": "2025-01-17T10:15:00.000Z",
    "sessionIds": ["session1", "session2"]
  }
]
```

**Register Remote (called by remotes):**
```http
POST /api/remotes/register
Authorization: Basic <HQ credentials>
Content-Type: application/json

{
  "id": "unique-id",
  "name": "remote-name",
  "url": "http://remote:4020",
  "token": "bearer-token-for-hq-to-use"
}
```

**Refresh Sessions (called by remotes):**
```http
POST /api/remotes/{remoteName}/refresh-sessions
Authorization: Basic <HQ credentials>
Content-Type: application/json

{
  "action": "created" | "deleted",
  "sessionId": "session-id"
}
```

### Session Management Through HQ

**Create Session on Remote:**
```http
POST /api/sessions
Content-Type: application/json

{
  "command": ["bash"],
  "remoteId": "remote-uuid",  // Specify which remote
  "name": "My Session"
}
```

**All Standard Endpoints Work Transparently:**
- `GET /api/sessions` - Aggregates from all remotes
- `GET /api/sessions/:id` - Proxied to owning remote
- `POST /api/sessions/:id/input` - Proxied to owning remote
- `DELETE /api/sessions/:id` - Proxied to owning remote
- `GET /ws` (WebSocket) - Unified v3 terminal stream proxied/fanned-out via HQ

## Authentication Flow

1. **Client → HQ**: Standard authentication (Basic Auth or JWT)
2. **HQ → Remote**: Bearer token (provided by remote during registration)
3. **Remote → HQ**: Basic Auth (HQ credentials)

## WebSocket Support

- Unified terminal transport: `/ws` (WebSocket v3 framing)
- HQ keeps one upstream `/ws` connection per remote and fans out frames to clients
- Subscription flags are aggregated per session (stdout/snapshots/events)

## Implementation Details

### Key Components

**RemoteRegistry** (`src/server/services/remote-registry.ts`):
- Maintains map of registered remotes
- Tracks session ownership (which sessions belong to which remote)
- Performs periodic health checks
- Handles registration/unregistration

**HQClient** (`src/server/services/hq-client.ts`):
- Used by remote servers to register with HQ
- Handles registration and cleanup
- Manages bearer token generation

**Session Routes** (`src/server/routes/sessions.ts`):
- Checks if running in HQ mode
- For remote sessions, forwards requests using bearer token
- For local sessions, handles normally

**Control Directory Watcher** (`src/server/services/control-dir-watcher.ts`):
- Watches for new/deleted sessions
- Notifies HQ about session changes
- Triggers session list refresh on HQ

### Session Tracking

- Each remote maintains its own session IDs
- HQ tracks which sessions belong to which remote
- Session IDs are not namespaced - they remain unchanged
- The `source` field in session objects indicates the remote name

## Testing

The e2e tests in `src/test/e2e/hq-mode.e2e.test.ts` demonstrate:
1. Starting HQ server and multiple remotes
2. Remote registration
3. Creating sessions on specific remotes
4. Proxying session operations
5. WebSocket buffer aggregation
6. Cleanup and unregistration

## Limitations

- Remotes must be network-accessible from the HQ server
- If the default hostname is not resolvable from HQ, set `--remote-url` to a reachable origin such as a tailnet address
- Health checks use a fixed 15-second interval
- No built-in load balancing (clients must specify remoteId)
- Bearer tokens are generated per server startup (not persistent)

## Security Considerations

- Always use HTTPS in production (use `--allow-insecure-hq` only for local dev)
- Use HTTPS for `--remote-url` on untrusted networks because HQ sends the remote bearer token on callback requests
- Bearer tokens are sensitive - they allow HQ to execute commands on remotes
- Remote listing and registration responses expose only public machine metadata, never callback bearer tokens
- HQ credentials should be strong and kept secure
- Consider network isolation between HQ and remotes
- Remotes should not be directly accessible from the internet

---

### Introduction (`docs/introduction.mdx`)

# Welcome to VibeTunnel Documentation

VibeTunnel is a powerful terminal sharing application that allows you to access your terminal sessions through any web browser. Whether you're on macOS, Linux, iPad, iPhone, or any other device with a web browser, VibeTunnel makes your terminal accessible from anywhere.

## Key Features

- **Remote Terminal Access**: Access your terminal from any web browser
- **Cross-Platform**: Native macOS app with Swift/SwiftUI, plus Linux support via npm package
- **Secure Connection**: End-to-end encrypted terminal sessions
- **Multi-Device**: Works on iOS, iPadOS, and any modern web browser
- **Real-time Sync**: Changes sync instantly across all connected devices
- **Headless Mode**: Run on servers and cloud instances without a GUI

## Quick Start

### For macOS
1. [Download VibeTunnel](https://vibetunnel.com/download) for macOS
2. Launch the app and create your first terminal session
3. Access your terminal from any device at your unique VibeTunnel URL

### For Linux & Headless Environments
1. Install via npm: `npm install vibetunnel`
2. Run the server: `vibetunnel-server`
3. Access your terminal at `http://localhost:4020`

## Documentation Overview

<CardGroup cols={2}>
  <Card title="Getting Started" icon="rocket" href="/docs/project-overview">
    Installation guides and your first steps with VibeTunnel
  </Card>
  <Card title="Architecture" icon="building" href="/docs/ARCHITECTURE">
    Learn about VibeTunnel's system design and technical details
  </Card>
  <Card title="Development" icon="code" href="/docs/development">
    Build, test, and contribute to VibeTunnel
  </Card>
  <Card title="API Reference" icon="book" href="/docs/spec">
    Complete API documentation and specifications
  </Card>
</CardGroup>

## Join Our Community

- [GitHub](https://github.com/amantus-ai/vibetunnel) - Star us on GitHub
- [Discord](https://discord.gg/vibetunnel) - Join our Discord community
- [Twitter](https://twitter.com/vibetunnel) - Follow us for updates

Ready to get started? Check out our [installation guide](/mac/README) or dive into the [architecture overview](/docs/ARCHITECTURE).

---

### Ios Spec (`docs/ios-spec.md`)

# VibeTunnel iOS/iPadOS Native App Specification

## Overview

VibeTunnel iOS is a native SwiftUI application that provides a beautiful, native interface to the VibeTunnel terminal multiplexer backend. The app allows users to create, manage, and interact with terminal sessions on their Mac from their iOS/iPadOS devices.

## Target Platform

- **iOS/iPadOS**: 18.0+
- **Universal App**: Single app that adapts to iPhone and iPad
- **SwiftUI**: Modern declarative UI framework
- **Terminal Engine**: ghostty-web (WASM + canvas)

## Core Features

### 1. Connection Management
- **Initial Setup Dialog**:
  - Server URL/IP input field
  - Port configuration (default: 3000)
  - Connection testing with status feedback
  - Saved connections list (stored in UserDefaults/KeyChain)
  - Auto-reconnection on app launch

### 2. Session Management
- **Session List View**:
  - Display all active and exited sessions
  - Real-time status updates (auto-refresh every 3 seconds)
  - Session cards showing:
    - Session name/command
    - Working directory
    - Status (running/exited)
    - PID (if running)
    - Exit code (if exited)
    - Started time
    - Last modified time
  - Pull-to-refresh functionality
  - Search/filter capabilities

- **Session Actions**:
  - Create new session
  - Kill running session
  - Clean up exited session files
  - Clean up all exited sessions
  - Tap to view terminal

### 3. Terminal View
- **ghostty-web Integration**:
  - Full terminal emulation using ghostty-web
  - Support for ANSI escape sequences
  - 256-color and true color support
  - VS Code dark theme colors
  - Font: SF Mono or custom monospace fonts
  - Adjustable font size

- **Input/Output**:
  - Native iOS keyboard integration
  - Special keys toolbar (arrows, escape, tab, ctrl)
  - Optional OpenAI BYOK voice transcription that inserts text without executing it
  - Copy/paste support
  - URL detection and tap-to-open
  - Smooth scrolling with momentum
  - Pinch-to-zoom font sizing

- **Real-time Updates**:
  - WebSocket buffer streaming for terminal output
  - Efficient buffer management
  - Auto-scroll to bottom on new output
  - Scroll position indicator

### 4. Session Creation
- **New Session Form**:
  - Command input (default: zsh)
  - Working directory picker
  - Session name (optional)
  - Terminal dimensions (auto-calculated based on device)
  - Recent commands/directories

### 5. iPad-Specific Features
- **Split View Support**:
  - Session list in sidebar
  - Terminal in main view
  - Multiple terminal tabs
  - Drag and drop support

- **Keyboard Support**:
  - Hardware keyboard shortcuts
  - Command+T for new session
  - Command+W to close session
  - Command+K to clear terminal

- **Multitasking**:
  - Slide Over support
  - Split View with other apps
  - Stage Manager compatibility

## Technical Architecture

### 1. Project Structure
```
ios/
├── VibeTunnel/
│   ├── App/
│   │   ├── VibeTunnelApp.swift
│   │   └── ContentView.swift
│   ├── Models/
│   │   ├── Session.swift
│   │   ├── ServerConfig.swift
│   │   └── TerminalData.swift
│   ├── Views/
│   │   ├── Connection/
│   │   │   ├── ConnectionView.swift
│   │   │   └── ServerConfigForm.swift
│   │   ├── Sessions/
│   │   │   ├── SessionListView.swift
│   │   │   ├── SessionCardView.swift
│   │   │   └── SessionCreateView.swift
│   │   ├── Terminal/
│   │   │   ├── TerminalView.swift
│   │   │   ├── GhosttyWebView.swift
│   │   │   ├── TerminalBufferRenderer.swift
│   │   │   └── TerminalToolbar.swift
│   │   └── Common/
│   │       ├── LoadingView.swift
│   │       └── ErrorView.swift
│   ├── Services/
│   │   ├── APIClient.swift
│   │   ├── SessionService.swift
│   │   ├── TerminalService.swift
│   │   └── BufferWebSocketClient.swift
│   ├── Utils/
│   │   ├── KeychainHelper.swift
│   │   ├── ColorTheme.swift
│   │   └── Extensions/
│   └── Resources/
│       ├── Assets.xcassets
│       └── Info.plist
└── VibeTunnel.xcodeproj
```

### 2. Data Models

```swift
// Session Model
struct Session: Codable, Identifiable {
    let id: String
    let command: String
    let workingDir: String
    let name: String?
    let status: SessionStatus
    let exitCode: Int?
    let startedAt: Date
    let lastModified: Date
    let pid: Int?
    let waiting: Bool?
    let width: Int?
    let height: Int?
}

enum SessionStatus: String, Codable {
    case running
    case exited
}

// Server Configuration
struct ServerConfig: Codable {
    let host: String
    let port: Int
    let name: String?
    
    var baseURL: URL {
        URL(string: "http://\(host):\(port)")!
    }
}

// Terminal Event
struct TerminalEvent {
    let timestamp: Double
    let type: EventType
    let data: String
    
    enum EventType: String {
        case output = "o"
        case input = "i"
        case resize = "r"
        case marker = "m"
    }
}
```

### 3. API Integration

```swift
// API Client Protocol
protocol APIClientProtocol {
    func getSessions() async throws -> [Session]
    func createSession(_ data: SessionCreateData) async throws -> String
    func killSession(_ sessionId: String) async throws
    func cleanupSession(_ sessionId: String) async throws
    func cleanupAllExitedSessions() async throws -> [String]
    func sendInput(sessionId: String, text: String) async throws
    func resizeTerminal(sessionId: String, cols: Int, rows: Int) async throws
}

// Buffer WebSocket client for terminal streaming
class BufferWebSocketClient: NSObject {
    func connect()
    func subscribe(to sessionId: String, handler: @escaping (TerminalWebSocketEvent) -> Void)
    func disconnect()
}
```

### 4. ghostty-web Integration

```swift
// Terminal View Controller
class TerminalViewController: UIViewController {
    private let terminal: GhosttyWebView
    private let sessionId: String
    private let bufferClient: BufferWebSocketClient
    
    // Configure terminal with VS Code theme
    // Handle input/output
    // Manage resize events
    // Stream terminal data via WebSocket buffer updates
}
```

### 5. State Management
- Use SwiftUI's `@StateObject` and `@ObservedObject` for view models
- Combine framework for reactive updates
- AsyncStream/Task for WebSocket handling
- UserDefaults for connection preferences
- Keychain for secure credential storage

## UI/UX Design

### 1. Design System
- **Colors**: Match VS Code dark theme
  - Background: #1e1e1e
  - Foreground: #d4d4d4
  - Accent: System blue
  - Success: System green
  - Error: System red

- **Typography**:
  - System fonts for UI
  - SF Mono for terminal
  - Dynamic Type support

- **Components**:
  - Native SwiftUI components
  - Consistent padding and spacing
  - Smooth animations and transitions

### 2. Navigation Flow
```
ConnectionView (if not connected)
    ↓
SessionListView (main screen)
    ├→ SessionCreateView (modal)
    └→ TerminalView (push/detail)
         └→ TerminalToolbar (overlay)
```

### 3. Responsive Design
- Adaptive layouts for different device sizes
- Compact/Regular size class handling
- Landscape optimization
- Dynamic terminal sizing

## Implementation Phases

### Phase 1: Foundation (Tasks 2-4)
- Set up Xcode project with SwiftUI
- Create basic navigation structure
- Implement connection dialog
- Build data models and API client
- Store server configuration

### Phase 2: Session Management (Task 5, 8)
- Session list view with real-time updates
- Session card components
- Create new session form
- Kill and cleanup actions
- Pull-to-refresh

### Phase 3: Terminal Integration (Tasks 6-7, 9)
- Integrate ghostty-web resources
- Terminal view wrapper
- WebSocket buffer streaming client
- Input handling
- Resize support

### Phase 4: Polish & iPad (Task 10)
- iPad-specific layouts
- Keyboard shortcuts
- Settings view
- Connection management
- Performance optimization

## Testing Strategy

### Unit Tests
- API client methods
- Data model parsing
- Session state management
- URL construction

### UI Tests
- Connection flow
- Session creation
- Terminal interaction
- Error handling

### Integration Tests
- End-to-end session lifecycle
- WebSocket streaming reliability
- Terminal command execution

## Security Considerations

- Use HTTPS when possible (with option for HTTP in local network)
- Store credentials in Keychain
- Validate server certificates
- Sanitize terminal output
- Handle authentication if backend requires it

## Performance Optimization

- Lazy loading of session list
- Efficient terminal buffer management
- Debounced resize events
- Background session updates
- Memory-efficient buffer streaming

## Future Enhancements

1. **Multiple Connections**: Support multiple VibeTunnel servers
2. **Session Sharing**: Share terminal sessions with others
3. **Recording**: Record and playback terminal sessions
4. **Themes**: Additional color themes beyond VS Code
5. **Shortcuts**: Customizable keyboard shortcuts
6. **File Transfer**: Upload/download files through the app
7. **Notifications**: Background notifications for session events

## Dependencies

- **ghostty-web**: Terminal emulator engine
- **Alamofire** (optional): For networking (or use URLSession)
- **KeychainSwift**: Secure credential storage

## App Store Considerations

- Ensure compliance with App Store guidelines
- Proper error handling and user feedback
- Privacy policy for network usage
- Export compliance for encryption

## Conclusion

This specification outlines a comprehensive native iOS/iPadOS client for VibeTunnel that leverages SwiftUI and ghostty-web to provide a superior terminal experience compared to the web interface. The app will be fast, responsive, and take full advantage of native iOS features while maintaining feature parity with the web frontend.

---

### Keyboard Shortcuts (`docs/keyboard-shortcuts.md`)

# VibeTunnel Keyboard Shortcuts

VibeTunnel provides a comprehensive set of keyboard shortcuts for efficient terminal session management. The app intelligently handles keyboard input to balance browser functionality with terminal control.

## Keyboard Capture Modes

VibeTunnel operates in two keyboard capture modes:

### 1. **Capture Active** (Default)
- Most keyboard shortcuts are sent to the terminal
- Critical browser shortcuts remain functional
- Indicated by the keyboard icon in the session header

### 2. **Capture Disabled**
- Browser shortcuts take precedence
- Terminal receives only typed text
- Toggle with double-press Escape

## Toggling Keyboard Capture

| Action | Description |
|--------|-------------|
| **Double Escape** | Toggle keyboard capture on/off |

Press Escape twice within 500ms to toggle between capture modes.

## Mobile Quick Keys

On touch devices, open **Settings → Mobile Quick Keys → Customize** to reorder or hide the terminal shortcut buttons. VibeTunnel includes Default and Compact layouts, stores the selected layout in the current browser, and keeps the Done button fixed so the software keyboard can always be dismissed.

## Critical Browser Shortcuts (Always Available)

These shortcuts always work, regardless of keyboard capture state:

### Tab Management
| macOS | Windows/Linux | Action |
|-------|---------------|--------|
| ⌘T | Ctrl+T | New tab |
| ⌘W | Ctrl+W | Close tab |
| ⌘⇧T | Ctrl+Shift+T | Reopen closed tab |
| ⌘1-9 | Ctrl+1-9 | Switch to tab 1-9* |
| ⌘0 | Ctrl+0 | Switch to last tab* |

*When keyboard capture is active in session view, these shortcuts switch between VibeTunnel sessions instead of browser tabs

### Window Management
| macOS | Windows/Linux | Action |
|-------|---------------|--------|
| ⌘N | Ctrl+N | New window |
| ⌘⇧N | Ctrl+Shift+N | New incognito window |
| ⌘Q | Ctrl+Q | Quit browser |
| ⌘H | - | Hide window (macOS) |
| - | Alt+F4 | Close window (Windows) |

### Essential Operations
| macOS | Windows/Linux | Action |
|-------|---------------|--------|
| ⌘C | Ctrl+C | Copy |
| ⌘V | Ctrl+V | Paste |
| ⌘A | Ctrl+A | Select all* |
| ⌘, | - | Preferences (macOS) |

*When capture is active, ⌘A/Ctrl+A goes to terminal (moves cursor to line start)

### Developer Tools
| macOS | Windows/Linux | Action |
|-------|---------------|--------|
| F12 | F12 | Open DevTools |
| ⌘⌥I | Ctrl+Shift+I | Open DevTools |

## VibeTunnel-Specific Shortcuts

### Navigation
| macOS | Windows/Linux | Action | Context |
|-------|---------------|--------|---------|
| ⌘K | Ctrl+K | Create new session | Any view |
| ⌘O | Ctrl+O | Browse files | List view |
| ⌘B | Ctrl+B | Toggle sidebar | Any view |
| Escape | Escape | Return to list | Session/File browser |

### Session Switching (When Keyboard Capture Active)
| macOS | Windows/Linux | Action | Context |
|-------|---------------|--------|---------|
| ⌘1...9 | Ctrl+1...9 | Switch to session 1 to 9 | Session view with capture ON |
| ⌘0 | Ctrl+0 | Switch to session 10 | Session view with capture ON |

**Note**: When keyboard capture is active in session view, number shortcuts switch between VibeTunnel sessions instead of browser tabs. The session numbers correspond to the numbers shown in the session list. This allows quick navigation between active sessions without leaving the keyboard.

## Terminal Shortcuts (When Capture Active)

When keyboard capture is active, these shortcuts are sent to the terminal:

### Cursor Movement
| macOS | Windows/Linux | Terminal Action |
|-------|---------------|-----------------|
| ⌘A | Ctrl+A | Move to line start |
| ⌘E | Ctrl+E | Move to line end |
| ⌥← | Alt+Left | Move word backward |
| ⌥→ | Alt+Right | Move word forward |

### Text Editing
| macOS | Windows/Linux | Terminal Action |
|-------|---------------|-----------------|
| ⌘W | Ctrl+W | Delete word backward |
| ⌘U | Ctrl+U | Delete to line start |
| ⌘K | Ctrl+K | Delete to line end |
| ⌥⌫ | Alt+Backspace | Delete word backward |
| ⌥D | Alt+D | Delete word forward |

### History & Search
| macOS | Windows/Linux | Terminal Action |
|-------|---------------|-----------------|
| ⌘R | Ctrl+R | Reverse history search |
| ⌘P | Ctrl+P | Previous command |
| ⌘N | Ctrl+N | Next command |

### Terminal Control
| macOS | Windows/Linux | Terminal Action |
|-------|---------------|-----------------|
| ⌘L | Ctrl+L | Clear screen |
| ⌘D | Ctrl+D | EOF/Exit |
| ⌘C | Ctrl+C | Interrupt process |
| ⌘Z | Ctrl+Z | Suspend process |

## Shortcuts Behavior by Capture State

### When Capture is Active ✅
These shortcuts go to the terminal:
- Text editing (⌘A, ⌘E, ⌘W, ⌘K, ⌘U)
- Navigation (⌘F, ⌘B for forward/backward char)
- Terminal control (⌘L, ⌘D, ⌘R)

### When Capture is Disabled ❌
These shortcuts perform browser actions:
- ⌘F/Ctrl+F - Find in page
- ⌘L/Ctrl+L - Focus address bar
- ⌘D/Ctrl+D - Bookmark page
- ⌘P/Ctrl+P - Print
- ⌘S/Ctrl+S - Save page

## Special Key Handling

### Modified Enter Key
| Combination | Terminal Receives |
|-------------|-------------------|
| Enter | Standard return |
| Ctrl+Enter | Special control sequence |
| Shift+Enter | Special shift sequence |

### Function Keys
- F1-F12 are sent to the terminal when capture is active
- F5 (Refresh) works in browser when capture is disabled
- F11 (Fullscreen) always works

## Platform Differences

### macOS
- Uses ⌘ (Command) as primary modifier
- ⌥ (Option) for word navigation
- Additional shortcuts like ⌘H (Hide), ⌘M (Minimize)

### Windows/Linux
- Uses Ctrl as primary modifier
- Alt for word navigation
- Alt+F4 closes windows

## Tips

1. **Double-tap Escape** to quickly toggle between terminal and browser shortcuts
2. **Critical shortcuts** (new tab, close tab, copy/paste) always work
3. **Session switching** (⌘1-9, ⌘0) - When keyboard capture is ON in session view, quickly switch between active sessions
4. **Tab switching** (⌘1-9, ⌘0) - When keyboard capture is OFF, switch browser tabs as usual
5. When unsure, check the keyboard icon in the session header to see capture state

## Troubleshooting

### Shortcuts not working as expected?

1. **Check keyboard capture state** - Look for the keyboard icon in the session header
2. **Try double-escape** - Toggle capture mode on/off
3. **Browser shortcuts in terminal?** - Ensure keyboard capture is active
4. **Terminal shortcuts in browser?** - Disable keyboard capture with double-escape

### Copy/Paste issues?

- Standard copy/paste (⌘C/⌘V or Ctrl+C/Ctrl+V) always works
- For terminal copy mode, use the terminal's built-in shortcuts
- Right-click context menu is always available

## Implementation Details

The keyboard shortcut system is implemented in:
- `web/src/client/utils/browser-shortcuts.ts` - Centralized shortcut detection
- `web/src/client/components/session-view/input-manager.ts` - Terminal input handling
- `web/src/client/app.ts` - Application-level shortcut handling

The system uses a priority-based approach:
1. Critical browser shortcuts (highest priority)
2. VibeTunnel app shortcuts
3. Terminal shortcuts (when capture active)
4. Browser defaults (when capture disabled)

---

### Linux (`docs/linux.md`)

# Linux (Ubuntu) Development + NPM Usage

## Goals
- One command to bootstrap Linux dev
- npm install/build works without pnpm
- Avoid SEA on Linux by default (Node CLI path is more reliable)

## Quickstart (Ubuntu 24.04)
```bash
web/scripts/linux-bootstrap.sh
cd web
npm install
npm run build
```

## What the bootstrap does
- Installs system deps: `curl`, `ca-certificates`, `xz-utils`, `python3`, `make`, `g++`, `git`
- Installs `libpam0g-dev` for PAM auth native module
- Installs checksum-verified Node.js 24.16.0 if missing or too old
- Installs rustup and the Rust 1.97.0 toolchain pinned by `native/vt-fwd/rust-toolchain.toml`

## SEA on Linux (disabled by default)
SEA builds are skipped on Linux unless explicitly enabled.

Enable if you want to test SEA (not recommended on Linux):
```bash
VIBETUNNEL_BUILD_SEA=1 npm run build
# or
npm run build -- --build-sea
```

## PAM Authentication (optional)
- `authenticate-pam` is an optional dependency.
- If `libpam0g-dev` is present during install, PAM auth will be built and used.
- If it’s missing, VibeTunnel still runs; auth falls back to env/SSH methods.

To force PAM after installing deps:
```bash
cd web
npm rebuild authenticate-pam
```

## npmjs (global install)
```bash
npm install vibetunnel@beta
vibetunnel --help
```

Linux npm package runs the Node CLI wrapper (no SEA).
Systemd support is available:
```bash
vibetunnel systemd install
systemctl --user start vibetunnel
systemctl --user status vibetunnel
```

## Troubleshooting
- `pnpm` missing during build: use npm (`npm install && npm run build`) or install pnpm.
- `cargo` or `rustc` missing: rerun `web/scripts/linux-bootstrap.sh`.
- `pam_appl.h` missing: `sudo apt-get install -y libpam0g-dev`.

---

### Logging Style Guide (`docs/logging-style-guide.md`)

# VibeTunnel Logging Style Guide

## Logging style

### 1. No Colors in Error/Warn
```typescript
// ❌ BAD
logger.error(chalk.red('Failed to connect'));
logger.warn(chalk.yellow('Missing config'));

// ✅ GOOD
logger.error('Failed to connect');
logger.warn('Missing config');
```

### 2. Use Colors in logger.log Only
```typescript
// Success = green
logger.log(chalk.green('Session created'));
logger.log(chalk.green(`Connected to ${server}`));

// Warning/Neutral = yellow
logger.log(chalk.yellow('Shutting down...'));
logger.log(chalk.yellow(`Client disconnected`));

// Info = blue
logger.log(chalk.blue('New client connected'));

// Metadata = gray
logger.log(chalk.gray('Debug mode enabled'));
```

### 3. Always Include Error Object
```typescript
// ❌ BAD
logger.error(`Failed: ${error.message}`);

// ✅ GOOD
logger.error('Failed to connect:', error);
```

### 4. Message Format
- Start with lowercase (except acronyms)
- No periods at end
- Be concise
- Include relevant IDs

```typescript
// ❌ BAD
logger.log('The session has been created successfully.');
logger.error('ERROR: Failed to connect to server!');

// ✅ GOOD
logger.log(`Session ${id} created`);
logger.error('Failed to connect to server');
```

### 5. No Prefixes or Tags
```typescript
// ❌ BAD
logger.log('[STREAM] Client connected');
logger.error('ERROR: Connection failed');
logger.warn('WARNING: Low memory');

// ✅ GOOD
logger.log('Client connected to stream');
logger.error('Connection failed');
logger.warn('Low memory');
```

## Common Patterns

### Lifecycle Events
```typescript
// Starting
logger.log(chalk.green('Server started'));
logger.log(chalk.green(`Session ${id} created`));

// Stopping
logger.log(chalk.yellow('Shutting down...'));
logger.log(chalk.yellow(`Session ${id} terminated`));

// Connections
logger.log(chalk.blue('Client connected'));
logger.log(chalk.yellow('Client disconnected'));
```

### Operations
```typescript
// Success
logger.log(chalk.green(`File uploaded: ${filename}`));

// In Progress
logger.log(`Processing ${count} items`);

// Failure
logger.error('Upload failed:', error);
```

### Debug (no colors needed)
```typescript
logger.debug(`Buffer size: ${size}`);
logger.debug(`Request headers: ${JSON.stringify(headers)}`);
```

## Quick Reference

| Event Type | Log Level | Color |
|------------|-----------|--------|
| Success | log | chalk.green |
| Connection | log | chalk.blue |
| Disconnect | log | chalk.yellow |
| Shutdown | log | chalk.yellow |
| Error | error | none |
| Warning | warn | none |
| Debug info | debug | none |
| Metadata | log | chalk.gray |

---

### Npm Release (`docs/npm-release.md`)

# NPM Release Checklist

This checklist ensures a smooth and error-free npm release process for VibeTunnel.

## Pre-Release Checklist

### 1. Code Quality
- [ ] Run all tests: `pnpm test`
- [ ] Run linting: `pnpm run lint`
- [ ] Run type checking: `pnpm run typecheck`
- [ ] Run format check: `pnpm run format:check`
- [ ] Fix any issues found: `pnpm run check:fix`

### 2. Dependency Updates
- [ ] Update all dependencies to latest versions
- [ ] Run `pnpm update --interactive --latest`
- [ ] Test thoroughly after updates
- [ ] Check for security vulnerabilities: `pnpm audit`

### 3. Version Updates (CRITICAL - Must be synchronized!)
- [ ] Update version in `web/package.json`
- [ ] Update version in `web/package.npm.json` (must match!)
- [ ] Update version in `mac/VibeTunnel/version.xcconfig` (MARKETING_VERSION)
- [ ] Update version in `ios/VibeTunnel/version.xcconfig` (if applicable)
- [ ] Ensure all versions match exactly

### 4. Changelog
- [ ] Update CHANGELOG.md with new features, fixes, and breaking changes
- [ ] Include migration guide for breaking changes
- [ ] Credit contributors

## Build Process

### 5. Clean Build
- [ ] Clean previous builds: `rm -rf dist-npm/ vibetunnel-*.tgz`
- [ ] Run the complete build on an Apple Silicon Mac with Docker available
- [ ] Run build: `pnpm run build:npm`
- [ ] Verify build output shows all platforms built successfully
- [ ] Check for "✅ authenticate-pam listed as optional dependency" in output

### 6. Package Verification (CRITICAL)
- [ ] Verify tarball exists: `ls -la vibetunnel-*.tgz`
- [ ] Extract package.json: `tar -xf vibetunnel-*.tgz package/package.json`
- [ ] Verify authenticate-pam is OPTIONAL:
  ```bash
  grep -A5 -B5 authenticate-pam package/package.json
  # Must show under "optionalDependencies", NOT "dependencies"
  ```
- [ ] Clean up: `rm -rf package/`
- [ ] Check package size is reasonable (~8-15 MB)

### 7. Package Contents Verification
- [ ] List package contents: `tar -tzf vibetunnel-*.tgz | head -50`
- [ ] Verify critical files are included:
  - [ ] `package/lib/vibetunnel-cli`
  - [ ] `package/lib/cli.js`
  - [ ] `package/bin/vibetunnel`
  - [ ] `package/bin/vt`
  - [ ] `package/scripts/postinstall.js`
  - [ ] `package/scripts/install-vt-command.js`
  - [ ] `package/node-pty/` directory
  - [ ] `package/prebuilds/` directory with .tar.gz files
  - [ ] `package/public/` directory

## Testing

### 8. Local Installation Test
- [ ] Test installation: `npm install ./vibetunnel-*.tgz`
- [ ] Verify version: `vibetunnel --version`
- [ ] Start server: `vibetunnel`
- [ ] Access web UI: http://localhost:4020
- [ ] Test vt command: `vt echo "test"`
- [ ] Uninstall: `npm uninstall -g vibetunnel`

### 9. Docker Test (Linux Compatibility)
- [ ] Create test Dockerfile:
  ```dockerfile
  FROM node:22-slim
  COPY vibetunnel-*.tgz /tmp/
  RUN npm install /tmp/vibetunnel-*.tgz
  CMD ["vibetunnel", "--version"]
  ```
- [ ] Build: `docker build -t vt-test .`
- [ ] Run: `docker run --rm vt-test`
- [ ] Test without PAM headers (should succeed)
- [ ] Test with PAM: Add `RUN apt-get update && apt-get install -y libpam0g-dev` before install

### 10. Cross-Platform Testing
- [ ] Test on macOS (if available)
- [ ] Test on Linux x64
- [ ] Test on Linux ARM64 (if available)
- [ ] Verify prebuilds are used (no compilation during install)

## Publishing

### 11. Pre-Publish Checks
- [ ] Ensure you're logged in to npm: `npm whoami`
- [ ] Check current tags: `npm dist-tag ls vibetunnel`
- [ ] Verify no uncommitted changes: `git status`
- [ ] Create git tag: `git tag v1.0.0-beta.X`

### 12. Publish (CRITICAL - Use tarball filename!)
- [ ] Publish beta: `npm publish vibetunnel-*.tgz --tag beta`
- [ ] Verify on npm: https://www.npmjs.com/package/vibetunnel
- [ ] Test installation from npm: `npm install vibetunnel@beta`

### 13. Post-Publish Verification
- [ ] Check package page shows correct version
- [ ] Verify optional dependencies are displayed correctly
- [ ] Test installation on clean system
- [ ] Monitor npm downloads and issues

### 14. Promotion to Latest (if stable)
- [ ] Wait for user feedback (at least 24 hours)
- [ ] If stable, promote: `npm dist-tag add vibetunnel@VERSION latest`
- [ ] Update documentation to reference new version

## Post-Release

### 15. Documentation Updates
- [ ] Update README.md with new version info
- [ ] Update installation instructions if needed
- [ ] Update web/docs/npm.md release history
- [ ] Create GitHub release with changelog

### 16. Communication
- [ ] Announce release on relevant channels
- [ ] Notify users of breaking changes
- [ ] Thank contributors

## Emergency Procedures

### If Wrong package.json Was Used
1. **DO NOT PANIC**
2. Check if authenticate-pam is a regular dependency (bad) or optional (good)
3. If bad, deprecate immediately:
   ```bash
   npm deprecate vibetunnel@VERSION "Installation issues on Linux. Use next version."
   ```
4. Increment version and republish following this checklist

### If Build Failed to Include Files
1. Check build logs for errors
2. Verify all copy operations in build-npm.js succeeded
3. Ensure no .gitignore or .npmignore is excluding files
4. Rebuild with verbose logging if needed

## Common Issues to Avoid

1. **NEVER use `npm publish` without tarball filename** - it may rebuild with wrong config
2. **ALWAYS verify authenticate-pam is optional** before publishing
3. **ALWAYS sync versions** across all config files
4. **NEVER skip the Docker test** - it catches Linux issues
5. **ALWAYS use beta tag first** - easier to fix issues before promoting to latest

## Version Numbering

- Beta releases: `1.0.0-beta.X` where X increments
- Patch releases: `1.0.0-beta.X.Y` where Y is patch number
- Stable releases: `1.0.0` (no beta suffix)

## Quick Commands Reference

```bash
# Update dependencies
pnpm update --interactive --latest

# Complete multi-platform build (macOS only)
pnpm run build:npm

# Verify
tar -xf vibetunnel-*.tgz package/package.json && \
grep -A5 optionalDependencies package/package.json && \
rm -rf package/

# Publish
npm publish vibetunnel-*.tgz --tag beta

# Promote to latest
npm dist-tag add vibetunnel@VERSION latest

# Check tags
npm dist-tag ls vibetunnel
```

## Release Frequency

- Beta releases: As needed for testing new features
- Stable releases: After thorough testing and user feedback
- Security patches: ASAP after discovery

Remember: It's better to delay a release than to publish a broken package!

---

### Openapi (`docs/openapi.md`)

# OpenAPI Migration Plan for VibeTunnel

## Overview

This document outlines the plan to adopt OpenAPI 3.1 for VibeTunnel's REST API to achieve type safety and consistency between the TypeScript server and Swift clients.

## Goals

1. **Single source of truth** - Define API contracts once in OpenAPI spec
2. **Type safety** - Generate TypeScript and Swift types from the spec
3. **Eliminate inconsistencies** - Fix type mismatches between platforms
4. **API documentation** - Auto-generate API docs from the spec
5. **Gradual adoption** - Migrate endpoint by endpoint without breaking changes

## Current Issues

- Session types differ completely between Mac app and server
- Git repository types have different field names and optional/required mismatches
- No standardized error response format
- Manual type definitions duplicated across platforms
- Runtime parsing errors due to type mismatches

## Implementation Plan

### Phase 1: Setup and Infrastructure (Week 1)

#### 1.1 Install Dependencies

```bash
# In web directory
pnpm add -D @hey-api/openapi-ts @apidevtools/swagger-cli @stoplight/spectral-cli
```

#### 1.2 Create Initial OpenAPI Spec

Create `web/openapi/openapi.yaml`:

```yaml
openapi: 3.1.0
info:
  title: VibeTunnel API
  version: 1.0.0
  description: Terminal sharing and remote access API
servers:
  - url: http://localhost:4020
    description: Local development server
```

#### 1.3 Setup Code Generation

**TypeScript Generation** (`web/package.json`):
```json
{
  "scripts": {
    "generate:api": "openapi-ts -i openapi/openapi.yaml -o src/generated/api",
    "validate:api": "spectral lint openapi/openapi.yaml",
    "prebuild": "npm run generate:api"
  }
}
```

**Swift Generation** (Xcode Build Phase):
1. Add `swift-openapi-generator` to Package.swift
2. Add build phase to run before compilation:
```bash
cd "$SRCROOT/../web" && \
swift-openapi-generator generate \
  openapi/openapi.yaml \
  --mode types \
  --mode client \
  --output-directory "$SRCROOT/Generated/OpenAPI"
```

#### 1.4 Create Shared Components

Define reusable schemas in `web/openapi/components/`:

```yaml
# components/errors.yaml
ErrorResponse:
  type: object
  required: [error, timestamp]
  properties:
    error:
      type: string
      description: Human-readable error message
    code:
      type: string
      description: Machine-readable error code
      enum: [
        'INVALID_REQUEST',
        'NOT_FOUND',
        'UNAUTHORIZED',
        'SERVER_ERROR'
      ]
    timestamp:
      type: string
      format: date-time
```

### Phase 2: Migrate Git Endpoints (Week 2)

Start with Git endpoints as they're well-defined and isolated.

#### 2.1 Define Git Schemas

```yaml
# openapi/paths/git.yaml
/api/git/repository-info:
  get:
    operationId: getRepositoryInfo
    tags: [git]
    parameters:
      - name: path
        in: query
        required: true
        schema:
          type: string
    responses:
      '200':
        description: Repository information
        content:
          application/json:
            schema:
              $ref: '../components/schemas.yaml#/GitRepositoryInfo'

# components/schemas.yaml
GitRepositoryInfo:
  type: object
  required: [isGitRepo, hasChanges, modifiedCount, untrackedCount, stagedCount, addedCount, deletedCount, aheadCount, behindCount, hasUpstream]
  properties:
    isGitRepo:
      type: boolean
    repoPath:
      type: string
    currentBranch:
      type: string
      nullable: true
    remoteUrl:
      type: string
      nullable: true
    githubUrl:
      type: string
      nullable: true
    hasChanges:
      type: boolean
    modifiedCount:
      type: integer
      minimum: 0
    untrackedCount:
      type: integer
      minimum: 0
    stagedCount:
      type: integer
      minimum: 0
    addedCount:
      type: integer
      minimum: 0
    deletedCount:
      type: integer
      minimum: 0
    aheadCount:
      type: integer
      minimum: 0
    behindCount:
      type: integer
      minimum: 0
    hasUpstream:
      type: boolean
```

#### 2.2 Update Server Implementation

```typescript
// src/server/routes/git.ts
import { paths } from '../../generated/api';

type GitRepositoryInfo = paths['/api/git/repository-info']['get']['responses']['200']['content']['application/json'];

router.get('/git/repository-info', async (req, res) => {
  const response: GitRepositoryInfo = {
    isGitRepo: true,
    repoPath: result.repoPath,
    // ... ensure all required fields are included
  };
  res.json(response);
});
```

#### 2.3 Update Mac Client

```swift
// Use generated types
import OpenAPIGenerated

let response = try await client.getRepositoryInfo(path: filePath)
let info = response.body.json // Fully typed!
```

### Phase 3: Migrate Session Endpoints (Week 3)

Session endpoints are more complex due to WebSocket integration.

#### 3.1 Standardize Session Types

```yaml
SessionInfo:
  type: object
  required: [id, name, workingDir, status, createdAt, pid]
  properties:
    id:
      type: string
      format: uuid
    name:
      type: string
    workingDir:
      type: string
    status:
      type: string
      enum: [starting, running, exited]
    exitCode:
      type: integer
      nullable: true
    createdAt:
      type: string
      format: date-time
    lastActivity:
      type: string
      format: date-time
    pid:
      type: integer
      nullable: true
    command:
      type: array
      items:
        type: string
```

#### 3.2 Create Session Operations

```yaml
/api/sessions:
  get:
    operationId: listSessions
    responses:
      '200':
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/SessionInfo'
  
  post:
    operationId: createSession
    requestBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CreateSessionRequest'
    responses:
      '201':
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SessionInfo'
```

### Phase 4: Runtime Validation (Week 4)

#### 4.1 Add Request Validation Middleware

```typescript
// src/server/middleware/openapi-validator.ts
import { OpenAPIValidator } from 'express-openapi-validator';

export const openapiValidator = OpenAPIValidator.middleware({
  apiSpec: './openapi/openapi.yaml',
  validateRequests: true,
  validateResponses: true,
});

// Apply to routes
app.use('/api', openapiValidator);
```

#### 4.2 Add Response Validation in Development

```typescript
// src/server/utils/validated-response.ts
export function validatedJson<T>(res: Response, data: T): void {
  if (process.env.NODE_ENV === 'development') {
    // Validate against OpenAPI schema
    validateResponse(res.req, data);
  }
  res.json(data);
}
```

### Phase 5: Documentation and Testing (Week 5)

#### 5.1 Generate API Documentation

```bash
# Add to package.json
"docs:api": "npx @redocly/cli build-docs openapi/openapi.yaml -o dist/api-docs.html"
```

#### 5.2 Add Contract Tests

```typescript
// src/test/contract/git-api.test.ts
import { matchesSchema } from './schema-matcher';

test('GET /api/git/repository-info matches schema', async () => {
  const response = await request(app)
    .get('/api/git/repository-info')
    .query({ path: '/test/repo' });
    
  expect(response.body).toMatchSchema('GitRepositoryInfo');
});
```

## Migration Checklist

### Endpoints to Migrate

- [ ] **Git APIs** (Phase 2)
  - [ ] GET /api/git/repo-info
  - [ ] GET /api/git/repository-info
  - [ ] GET /api/git/remote
  - [ ] GET /api/git/status
  - [ ] POST /api/git/event
  - [ ] GET /api/git/follow

- [ ] **Session APIs** (Phase 3)
  - [ ] GET /api/sessions
  - [ ] POST /api/sessions
  - [ ] GET /api/sessions/:id
  - [ ] DELETE /api/sessions/:id
  - [ ] POST /api/sessions/:id/resize
  - [ ] POST /api/sessions/:id/input
  - [ ] WS /ws (v3 framing; not OpenAPI)

- [ ] **Repository APIs** (Phase 4)
  - [ ] GET /api/repositories/discover
  - [ ] GET /api/repositories/branches

- [ ] **Worktree APIs** (Phase 4)
  - [ ] GET /api/worktrees
  - [ ] POST /api/worktrees
  - [ ] DELETE /api/worktrees/:branch
  - [ ] POST /api/worktrees/switch

## Success Metrics

1. **Zero runtime type errors** between Mac app and server
2. **100% API documentation** coverage
3. **Contract tests** for all endpoints
4. **Reduced code** - Remove manual type definitions
5. **Developer velocity** - Faster API development with code generation

## Long-term Considerations

### Future Enhancements

1. **GraphQL Gateway** - Add GraphQL layer on top of REST for complex queries
2. **API Versioning** - Use OpenAPI to manage v1/v2 migrations
3. **Client SDKs** - Generate SDKs for other platforms (iOS, CLI tools)
4. **Mock Server** - Use OpenAPI spec to run mock server for testing

### Breaking Changes

When making breaking changes:
1. Version the API (e.g., /api/v2/)
2. Deprecate old endpoints with sunset dates
3. Generate migration guides from schema differences

## Resources

- [OpenAPI 3.1 Specification](https://spec.openapis.org/oas/v3.1.0)
- [OpenAPI TypeScript Generator](https://github.com/hey-api/openapi-ts)
- [Swift OpenAPI Generator](https://github.com/apple/swift-openapi-generator)
- [Spectral Linting](https://stoplight.io/open-source/spectral)
- [ReDoc Documentation](https://redocly.com/docs/redoc)

---

### Org Migrate (`docs/org-migrate.md`)

# GitHub Organization Migration Plan

This document outlines the migration process for moving VibeTunnel from `amantus-ai/vibetunnel` to `vibetunnel/vibetunnel`.

**Status: TODO** - This migration has not been completed yet.

## Migration Options

### Option 1: Simple Transfer (GitHub Built-in)

The simplest approach using GitHub's native transfer feature.

#### What Transfers Automatically

✅ **Code & History**
- All branches and commit history
- Git tags and annotated tags

✅ **Project Management**
- Issues and pull requests (with all comments)
- Projects (classic and new)
- Releases and release assets
- Milestones and labels

✅ **Community Features**
- Stars and watchers
- Wiki content
- Fork relationships

✅ **Security & Integration**
- Webhooks configurations
- Deploy keys
- Repository-level secrets
- GitHub Actions workflows
- Git LFS objects (copied in background)

#### What Needs Manual Updates

⚠️ **Organization-level Settings**
- Branch protection rules (inherits new org defaults - review carefully)
- Organization-level secrets (must recreate in new org)
- Environment-level secrets (if used outside repo scope)
- Team permissions (reassign in new org structure)

⚠️ **External Integrations**
- CI/CD systems with hardcoded URLs
- Documentation with repository links
- Package registries (npm, etc.)
- External webhooks
- Status badges in README

### Option 2: Migration with History Cleanup

Since `https://github.com/vibetunnel/vibetunnel` may already exist, we can perform a clean migration that:
1. Removes large files from history
2. Cleans up accidental commits
3. Preserves important history
4. Maintains all issues, PRs, and project management features

Use BFG Repo-Cleaner or git-filter-repo to create a cleaned version of the repository.

## Pre-Migration Checklist

### Preparation (1-2 days before)

- [ ] **Prepare Target Organization**
  - Create `vibetunnel` organization if not exists
  - Set up teams and permissions structure
  - Configure organization-level settings
  - Review default branch protection rules

- [ ] **Audit Current Setup**
  - Document all webhooks and integrations
  - List organization/environment secrets
  - Note branch protection rules
  - Identify external services using the repo

- [ ] **Analyze Repository for Cleanup (if using Option 2)**
  ```bash
  # Find large files in history
  git rev-list --objects --all | \
    git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
    awk '/^blob/ && $3 > 10485760 {print $3, $4}' | \
    sort -rn | \
    numfmt --field=1 --to=iec
  ```

- [ ] **Notify Stakeholders**
  - Team members about the migration
  - Users via issue/discussion if needed
  - Update any public documentation

## Migration Process

### Option 1: Simple Transfer Steps

1. Navigate to **Settings → General → Danger Zone → Transfer**
2. Enter the new owner: `vibetunnel`
3. Type the repository name to confirm
4. Accept the invite from the destination org
5. Done! ✅

### Option 2: Clean Migration Script

Save this as `migrate-clean.sh`:

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Post-Migration Updates

### Update Git Remotes
```bash
# For all local clones
git remote set-url origin git@github.com:vibetunnel/vibetunnel.git

# Verify the change
git remote -v
```

### Update VibeTunnel Code

- [ ] Update `GITHUB_URL` in `mac/VibeTunnel/version.xcconfig`
- [ ] Update repository URLs in all `package.json` files:
  ```json
  {
    "repository": {
      "type": "git",
      "url": "git+https://github.com/vibetunnel/vibetunnel.git"
    },
    "bugs": {
      "url": "https://github.com/vibetunnel/vibetunnel/issues"
    },
    "homepage": "https://github.com/vibetunnel/vibetunnel#readme"
  }
  ```
- [ ] Update any hardcoded GitHub URLs in documentation
- [ ] Update CLAUDE.md references
- [ ] Update docs.json if it contains repository URLs

### Update External Services

- [ ] CI/CD configurations
- [ ] npm package registry URLs
- [ ] Monitoring services
- [ ] Documentation sites
- [ ] README badges and links
- [ ] Installation instructions
- [ ] Contributing guidelines

### Build & Release

- [ ] Update GitHub Actions secrets if needed
- [ ] Verify macOS notarization still works
- [ ] Test release workflow with new repo URL
- [ ] Update Sparkle appcast URLs if applicable
- [ ] Consider publishing a patch version with updated URLs

## Redirect Behavior

GitHub automatically sets up redirects:
- `https://github.com/amantus-ai/vibetunnel` → `https://github.com/vibetunnel/vibetunnel`
- Git operations: `git clone git@github.com:amantus-ai/vibetunnel.git` still works
- API calls to old URL redirect automatically

⚠️ **Redirect Limitations**:
- Redirects break if someone creates a new repo at `amantus-ai/vibetunnel`
- Some tools may not follow redirects properly
- Best practice: Update all references ASAP

## Timeline

**Day 1**: Preparation
- Set up new organization
- Audit current configuration
- Notify team

**Day 2**: Migration
- Morning: Final preparations
- Midday: Execute transfer
- Afternoon: Update configurations

**Day 3**: Verification
- Test all integrations
- Monitor for issues
- Complete documentation updates

## Important Notes

- GitHub's transfer process is well-tested and reliable
- The automatic redirects provide good backward compatibility
- If using history cleanup (Option 2):
  - This process rewrites history - all commit SHAs will change
  - Contributors will need to re-clone or rebase their work
  - Keep the cleaned backup for a few weeks
- Consider doing this during a low-activity period

## Rollback Plan

If issues arise:
1. GitHub Support can reverse transfers within a short window
2. Keep the migration backup (if using Option 2)
3. Document any issues for future reference

## References

- [GitHub Docs: Transferring a repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/transferring-a-repository)
- [BFG Repo-Cleaner](https://rtyley.github.io/bfg-repo-cleaner/) (for Option 2)
- [git-filter-repo](https://github.com/newren/git-filter-repo) (alternative to BFG)
- [GitHub: Removing sensitive data](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository)

---

### Push Impl (`docs/push-impl.md`)

# Push Notification Implementation Plan

This document outlines the comprehensive plan for improving VibeTunnel's notification system through two major initiatives:
1. Creating a dedicated Notifications tab in macOS settings
2. Migrating SessionMonitor from the Mac app to the server for unified notifications

## Overview

Currently, VibeTunnel has inconsistent notification implementations between the Mac and web clients. The Mac app has its own SessionMonitor while the web relies on server events. This leads to:
- Different notification behaviors between platforms
- Missing features (e.g., notification settings parity between platforms)
- Duplicate code and maintenance burden
- Inconsistent descriptions and thresholds

## Part 1: macOS Settings Redesign

### Current State
- Notification settings are cramped in the General tab
- No room for descriptive text explaining each notification type
- Settings are already at 710px height (quite tall)
- Missing helpful context that exists in the web UI

### Proposed Solution: Dedicated Notifications Tab

#### 1. Add Notifications Tab to SettingsTab enum

```swift
// SettingsTab.swift
enum SettingsTab: String, CaseIterable {
    case general
    case notifications  // NEW
    case quickStart
    case dashboard
    // ... rest of tabs
}

// Add display name and icon
var displayName: String {
    switch self {
    case .notifications: "Notifications"
    // ... rest
    }
}

var icon: String {
    switch self {
    case .notifications: "bell.badge"
    // ... rest
    }
}
```

#### 2. Create NotificationSettingsView.swift

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### 3. Create Reusable NotificationToggleRow Component

```swift
struct NotificationToggleRow: View {
    let title: String
    let description: String
    @Binding var isOn: Bool
    let helpText: String
    
    var body: some View {
        HStack(alignment: .top, spacing: 12) {
            VStack(alignment: .leading, spacing: 4) {
                HStack {
                    Toggle(title, isOn: $isOn)
                        .toggleStyle(.checkbox)
                    HelpTooltip(text: helpText)
                }
                Text(description)
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }
        }
        .padding(.vertical, 4)
    }
}
```

#### 4. Update SettingsView.swift

```swift
// Add the new tab
NotificationSettingsView()
    .tabItem {
        Label(SettingsTab.notifications.displayName, 
              systemImage: SettingsTab.notifications.icon)
    }
    .tag(SettingsTab.notifications)
```

#### 5. Update GeneralSettingsView.swift

Remove all notification-related settings to free up space.

### Standardized Notification Descriptions

Use these descriptions consistently across Mac and web:

| Type | Title | Description |
|------|-------|-------------|
| Session Start | Session starts | When a new session starts (useful for shared terminals) |
| Session Exit | Session ends | When a session terminates or crashes (shows exit code) |
| Command Error | Commands fail | When commands fail with non-zero exit codes |
| Command Completion | Commands complete (> 3 seconds) | When commands taking >3 seconds finish (builds, tests, etc.) |
| Terminal Bell | Terminal bell (🔔) | Terminal bell (^G) from vim, IRC mentions, completion sounds |

## Part 2: Server-Side SessionMonitor Migration

### Current Architecture

```
Mac App:
  SessionMonitor (Swift) → NotificationService → macOS notifications
  
Server:
  PtyManager → Basic events → WS v3 → Web notifications
```

### Proposed Architecture

```
Server:
  PtyManager → SessionMonitor (TypeScript) → Enhanced events → WS v3
                                                                    ↓
                                                        Mac & Web clients
```

### Implementation Steps

#### 1. Create Server-Side SessionMonitor

```typescript
// web/src/server/services/session-monitor.ts

export interface SessionState {
  id: string;
  name: string;
  command: string[];
  isRunning: boolean;
  commandStartTime?: Date;
  lastCommand?: string;
}

export class SessionMonitor {
  private sessions = new Map<string, SessionState>();
  private commandThresholdMs = 3000; // 3 seconds
  
  constructor(
    private ptyManager: PtyManager,
    private eventBus: EventEmitter
  ) {
    this.setupEventListeners();
  }
  
  // ... other monitoring methods
}
```

#### 2. Enhance Event Types

```typescript
// web/src/shared/types.ts

export enum ServerEventType {
  SessionStart = 'session-start',
  SessionExit = 'session-exit',
  CommandFinished = 'command-finished',
  CommandError = 'command-error',  // NEW - separate from finished
  Bell = 'bell',                   // NEW
  Connected = 'connected'
}

export interface ServerEvent {
  type: ServerEventType;
  timestamp: string;
  sessionId: string;
  sessionName?: string;
  
  // Event-specific data
  exitCode?: number;
  command?: string;
  duration?: number;
  message?: string;
}
```

#### 3. Integrate with PtyManager

```typescript
// web/src/server/pty/pty-manager.ts

class PtyManager {
  private sessionMonitor: SessionMonitor;
  
  constructor() {
    this.sessionMonitor = new SessionMonitor(this, serverEventBus);
  }
  
  // Feed data to SessionMonitor
  private handlePtyData(sessionId: string, data: string) {
    // Existing data handling...
    
    // Detect bell character
    if (data.includes('\x07')) {
      serverEventBus.emit('notification', {
        type: ServerEventType.Bell,
        sessionId,
        sessionName: this.sessions.get(sessionId)?.name
      });
    }
    
  }
}
```

#### 4. Update Server WS v3 Hub

```typescript
// web/src/server/services/ws-v3-hub.ts

// SessionMonitor emits ServerEvent objects.
// WsV3Hub broadcasts EVENT frames to clients that SUBSCRIBE with:
//   sessionId == "" and flags include WsV3SubscribeFlags.Events
```

#### 5. Update Mac NotificationService

```swift
// NotificationService.swift

class NotificationService {
    // Remove local SessionMonitor dependency
    // Subscribe to server WS v3 events instead
    
    private func connectToServerEvents() {
        // WS v3: connect `/ws`, send SUBSCRIBE(sessionId:"", flags: Events)
    }
    
    private func handleServerEvent(_ event: ServerEvent) {
        // Map server events to notifications
        switch event.type {
        case .sessionStart:
            if preferences.sessionStart {
                sendNotification(for: event)
            }
        // ... handle other event types
        }
    }
}
```

#### 6. Update Web Notification Service

```typescript
// web/src/client/services/push-notification-service.ts

private handleServerEvent(event: ServerEvent) {
  if (!this.preferences[this.mapEventTypeToPreference(event.type)]) {
    return;
  }
  
  // Send browser notification
  this.showNotification(event);
}

private mapEventTypeToPreference(type: ServerEventType): keyof NotificationPreferences {
  const mapping = {
    [ServerEventType.SessionStart]: 'sessionStart',
    [ServerEventType.SessionExit]: 'sessionExit',
    [ServerEventType.CommandFinished]: 'commandCompletion',
    [ServerEventType.CommandError]: 'commandError',
    [ServerEventType.Bell]: 'bell'
  };
  return mapping[type];
}
```

## Migration Strategy

### Phase 1: Preparation (Non-breaking)
1. Implement server-side SessionMonitor alongside existing system
2. Add new event types to shared types

### Phase 2: Server Enhancement (Non-breaking)
1. Deploy enhanced server with SessionMonitor
2. Server emits both old and new event formats
3. Test with web client to ensure compatibility

### Phase 3: Mac App Migration
1. Update Mac app to consume server events
2. Keep fallback to local monitoring if server unavailable
3. Remove local SessionMonitor once stable

### Phase 4: Cleanup
1. Remove old event formats from server
2. Remove local SessionMonitor code from Mac
3. Document new architecture

## Testing Plan

### Unit Tests
- Event threshold calculations
- Activity state transitions

### Integration Tests
- Server events reach both Mac and web clients
- Notification preferences are respected
- Bell character detection

### Manual Testing
- Test each notification type on both platforms
- Verify descriptions match
- Test with multiple clients connected
- Test offline Mac app behavior

## Success Metrics

1. **Consistency**: Same notifications appear on Mac and web for same events
2. **Performance**: No noticeable lag in notifications
3. **Reliability**: No missed notifications
4. **Maintainability**: Single codebase for monitoring logic

## Timeline Estimate

- **Week 1**: Implement macOS Notifications tab
- **Week 2**: Create server-side SessionMonitor
- **Week 3**: Integrate and test with web client
- **Week 4**: Migrate Mac app and testing
- **Week 5**: Polish, documentation, and deployment

## Risks and Mitigations

| Risk | Impact | Mitigation |
|------|--------|------------|
| Breaking existing notifications | High | Phased rollout, maintain backwards compatibility |
| Performance impact on server | Medium | Efficient event handling, consider debouncing |
| Mac app offline mode | Medium | Keep local fallback for critical notifications |
| Complex migration | Medium | Detailed testing plan, feature flags |

## Conclusion

This two-part implementation will:
1. Provide a better UI for notification settings on macOS
2. Create a unified notification system across all platforms
3. Reduce code duplication and maintenance burden
4. Ensure consistent behavior for all users

The migration is designed to be non-breaking with careful phases to minimize risk.

---

### Push Notification (`docs/push-notification.md`)

# Push Notifications in VibeTunnel

VibeTunnel provides real-time alerts for terminal events via native macOS notifications and web push notifications. The system is primarily driven by the **Session Monitor**, which tracks terminal activity and triggers alerts.

## How It Works

The **Session Monitor** is the core of the notification system. It observes terminal sessions for key events and dispatches them to the appropriate notification service (macOS or web).

### Notification Settings Explained

When you enable notifications in VibeTunnel, you can choose which events to be notified about:

#### 1. Session starts ✓
- **Notification**: "Session Started" with the session name
- **Triggers when**: A new terminal session is created
- **Use case**: Know when someone starts using your shared terminal

#### 2. Session ends ✓
- **Notification**: "Session Ended" with the session name
- **Shows exit code**: If the session crashed or exited abnormally
- **Triggers when**: A terminal session closes
- **Use case**: Monitor when sessions terminate, especially if unexpected

#### 3. Commands complete (> 3 seconds) ✓
- **Notification**: "Your Turn" with the command that finished
- **Shows duration**: How long the command took to complete
- **Triggers when**: Any command that runs for more than 3 seconds finishes
- **Use case**: Get notified when long builds, tests, or scripts complete

#### 4. Commands fail ✓
- **Notification**: "Command Failed" with the failed command
- **Shows exit code**: The specific error code returned
- **Triggers when**: Any command returns a non-zero exit code
- **Use case**: Immediately know when something goes wrong

#### 5. Terminal bell (\u{0007}) ✓
- **Notification**: "Terminal Bell" with the session name
- **Triggers when**: A program outputs the bell character (ASCII 7/^G)
- **Common sources**: vim alerts, IRC mentions, completion sounds
- **Use case**: Get alerts from terminal programs that use the bell

## Architecture

### System Overview

The notification system in VibeTunnel follows a layered architecture:

```
Terminal Events → Session Monitor → Event Processing → Notification Service → OS/Browser
```

### Key Components

#### 1. Session Monitor (`SessionMonitor.swift`)
- **Role**: Tracks all terminal sessions and their state changes
- **Key responsibilities**:
  - Monitors session lifecycle (start/exit)
  - Tracks command execution and duration
  - Filters events based on thresholds (e.g., 3-second rule)

#### 2. Server Event System (`ServerEvent.swift`)
- **Event types**: 
  - `sessionStart`, `sessionExit`: Session lifecycle
  - `commandFinished`, `commandError`: Command execution
  - `bell`: Terminal bell character detection
- **Event data**: Each event carries session ID, display name, duration, exit codes, etc.

#### 3. Notification Service (`NotificationService.swift`)
- **macOS integration**: Uses `UserNotifications` framework
- **Event source**: Connects to server via WebSocket v3 at `/ws` (global subscription for `EVENT` frames)
- **Preference management**: Checks user settings before sending
- **Permission handling**: Manages notification authorization

#### 4. Configuration Manager (`ConfigManager.swift`)
- **Settings storage**: Persists user notification preferences
- **Default values**: All notification types enabled by default
- **Real-time updates**: Changes take effect immediately

### Event Flow

1. **Terminal Activity**: User runs commands, sessions start/stop
2. **Event Detection**: Session Monitor detects changes
3. **Event Creation**: Creates typed `ServerEvent` objects
4. **Filtering**: Checks user preferences and thresholds
5. **Notification Dispatch**: Sends to OS notification center
6. **User Interaction**: Shows native macOS notifications

### Special Features

#### Command Duration Tracking
- Only notifies for commands > 3 seconds
- Tracks start time when command begins
- Calculates duration on completion
- Formats duration for display (e.g., "5 seconds", "2 minutes")

#### Bell Character Detection
- Terminal emulator detects ASCII 7 (`\u{0007}`)
- Forwards bell events through WebSocket
- Server converts to notification event

## Native macOS Notifications

The VibeTunnel macOS app provides the most reliable and feature-rich notification experience.

- **Enable**: Go to `VibeTunnel Settings > General` and toggle **Show Session Notifications**.
- **Features**: Uses the native `UserNotifications` framework, respects Focus Modes, and works in the background.

## Web Push Notifications

For non-macOS clients or remote access, VibeTunnel supports web push notifications.

- **Enable**: Click the notification icon in the web UI and grant browser permission.
- **Technology**: Uses Service Workers and the Web Push API.

### HTTPS Requirement

⚠️ **Important**: Web push notifications require HTTPS to function. This is a security requirement enforced by all modern browsers.

- **Local development**: Works on `http://localhost:4020` without HTTPS
- **Remote access**: Requires HTTPS with a valid SSL certificate
- **Why**: Service Workers (which power push notifications) only work on secure origins to prevent man-in-the-middle attacks

### Enabling HTTPS for Remote Access

If you need web push notifications when accessing VibeTunnel remotely, you'll need to serve it over HTTPS. Here are some solutions:

#### Tailscale Serve (Recommended)
[Tailscale Serve](https://tailscale.com/kb/1242/tailscale-serve) is an excellent solution for automatically creating HTTPS connections within your network:

```bash
# Install Tailscale and connect to your network
# Then expose VibeTunnel with HTTPS:
tailscale serve https / http://localhost:4020
```

Benefits:
- Automatic HTTPS with valid certificates
- Works within your Tailscale network
- No port forwarding or firewall configuration needed
- Push notifications will work for all devices on your Tailnet

#### Other Options
- **Ngrok**: Provides HTTPS tunnels but requires external exposure
- **Cloudflare Tunnel**: Free HTTPS tunneling service
- **Let's Encrypt**: For permanent HTTPS setup with your own domain

## Troubleshooting

- **No Notifications**: Ensure they are enabled in both VibeTunnel settings and your OS/browser settings.
- **Duplicate Notifications**: You can clear old or duplicate subscriptions by deleting `~/.vibetunnel/notifications/subscriptions.json`.
- **Claude Notifications**: If Claude's "Your Turn" notifications aren't working, you can try forcing it to use the terminal bell:
  ```bash
  claude config set --global preferredNotifChannel terminal_bell
  ```

---

### Repoprompt (`docs/repoprompt.md`)

# RepoPrompt Pair Programming Guide

RepoPrompt is a powerful MCP (Model Context Protocol) server that enables sophisticated AI pair programming workflows. This guide explains how to use RepoPrompt with Claude Code for complex development tasks.

## Overview

The pair programming mode allows Claude Code to collaborate with other AI models (like OpenAI's O1/O3 or Google's Gemini) by:
- Claude Code acts as a **context manager** - gathering files, managing tokens, and handling tools
- Another AI model acts as the **planner/executor** - providing deep reasoning and implementation
- Both models work together continuously throughout the task

## Key Benefits

1. **Leverages Model Strengths**: Claude excels at tool use and navigation; reasoning models excel at complex problem-solving
2. **Optimal Context Management**: Reasoning models work best with complete context upfront rather than incremental discovery
3. **Continuous Collaboration**: Maintains conversation state and file context between messages
4. **Higher Quality Output**: More considered implementations for complex tasks

## Installation

### Prerequisites
- Claude Code with MCP support
- RepoPrompt MCP server installed

### Quick Setup
```bash
# Install RepoPrompt if not already installed
claude mcp add RepoPrompt -- /path/to/repoprompt_cli

# Verify installation
claude mcp list

# Restart Claude Code to load the server
# Use Cmd+R or restart from command line
```

## Basic Workflow

### 1. Select Your Working Files

First, identify and select files relevant to your task:

```
# List current selection
manage_selection action="list" include_stats=true

# Add specific files
manage_selection action="add" paths=["src/main.ts", "src/utils.ts", "tests/main.test.ts"]

# Or replace entire selection
manage_selection action="replace" paths=["src/auth/login.ts", "src/auth/session.ts"]

# Clear selection to start fresh
manage_selection action="clear"
```

**Tips:**
- Keep total context under 100K tokens (ideally ~60K)
- Select only directly relevant files
- Use `get_code_structure` to preview large files efficiently

### 2. Set Your Task Description

Write a clear prompt describing what you want to accomplish:

```
set_prompt_state prompt="Implement user authentication with JWT tokens, including login, logout, and session management"
```

### 3. Start the Pair Programming Session

Begin with planning mode to have the AI create a detailed implementation plan:

```
chat_send mode="plan" message="Let's implement the authentication system as described"
```

### 4. Execute the Plan

Switch to edit mode to implement the changes:

```
chat_send mode="edit" message="Now let's implement the authentication module"
```

## Advanced Features

### File Discovery Workflow

Before starting, use these tools to find relevant files:

```
# Get project structure
get_file_tree type="files"

# Search for specific patterns
search pattern="authentication" mode="both"

# Preview file structure without full content
get_code_structure paths=["src/auth.ts", "src/server.ts"]

# Read specific files
read_file path="src/config/auth.config.ts"
```

### Context Management Strategy

1. **Start Minimal**: Begin with core files only
2. **Add Dependencies**: Include related files as needed
3. **Monitor Token Count**: Check with `manage_selection action="list" include_stats=true`
4. **Update Between Tasks**: Use `replace` when switching focus

### Working with Multiple Models

RepoPrompt supports various AI models. List available presets:

```
list_models
```

Choose a specific model for your task:

```
chat_send mode="plan" model="DeepAnalysis" message="Analyze our authentication architecture"
```

## Practical Examples

### Example 1: Adding a New Feature

```
# 1. Clear previous context
manage_selection action="clear"

# 2. Find relevant files
search pattern="session manager" mode="both"

# 3. Select core files
manage_selection action="add" paths=["src/session-manager.ts", "src/types/session.ts", "tests/session.test.ts"]

# 4. Set the task
set_prompt_state prompt="Add session expiration and automatic cleanup features"

# 5. Plan the implementation
chat_send mode="plan" message="Design session expiration system with configurable timeouts"

# 6. Execute the plan
chat_send mode="edit" message="Implement the session expiration features"
```

### Example 2: Debugging Complex Issues

```
# 1. Gather error context
search pattern="error.*websocket" mode="content"

# 2. Select relevant files
manage_selection action="replace" paths=["src/websocket.ts", "src/error-handler.ts", "logs/recent-errors.log"]

# 3. Describe the issue
set_prompt_state prompt="WebSocket connections dropping intermittently under high load"

# 4. Analyze with reasoning model
chat_send mode="plan" message="Investigate and fix WebSocket stability issues"
```

### Example 3: Refactoring

```
# 1. Select files to refactor
manage_selection action="add" paths=["src/old-api.ts", "src/handlers/*.ts"]

# 2. Set refactoring goals
set_prompt_state prompt="Refactor API to use async/await instead of callbacks, maintain backward compatibility"

# 3. Plan the refactor
chat_send mode="plan" message="Create refactoring plan preserving all existing functionality"

# 4. Execute incrementally
chat_send mode="edit" message="Start refactoring the authentication handlers"
```

## Best Practices

### 1. Context Selection
- Start with minimal context and add as needed
- Include test files when modifying code
- Add configuration files for system-wide changes
- Remove files that are no longer relevant

### 2. Prompt Writing
- Be specific about requirements
- Include constraints (e.g., "maintain backward compatibility")
- Mention relevant technologies and patterns
- Specify testing requirements

### 3. Mode Selection
- Use `plan` mode for:
  - Complex architectural decisions
  - Multi-file refactoring
  - New feature design
  
- Use `edit` mode for:
  - Direct implementation
  - Bug fixes with clear solutions
  - Following an established plan

- Use `chat` mode for:
  - General discussions
  - Code exploration
  - Understanding existing code

### 4. Session Management
- Continue existing chats for related work:
  ```
  chat_tools action="list"
  chat_send chat_id="existing-id" message="Continue the refactoring"
  ```
- Start new chats for unrelated tasks
- Name your chats for easy identification:
  ```
  chat_send new_chat=true chat_name="Auth System Refactor" message="Starting auth refactor"
  ```

## Troubleshooting

### Common Issues

1. **Token Limit Exceeded**
   - Check current usage: `manage_selection action="list" include_stats=true`
   - Remove unnecessary files: `manage_selection action="remove" paths=["large-file.ts"]`
   - Use code structure instead of full content for large files

2. **Model Not Responding**
   - Reasoning models can be slow, especially on complex tasks
   - Wait for completion before sending follow-up messages
   - Consider breaking large tasks into smaller chunks

3. **Context Lost Between Messages**
   - Ensure you're continuing the same chat session
   - Verify file selection hasn't changed: `manage_selection action="list"`
   - Check chat history: `chat_tools action="log"`

4. **Edit Mode Not Working**
   - Ensure you have write permissions for target files
   - Verify files exist and are selected
   - Check for syntax errors in previous edits

### Getting Help

- Use `chat_tools action="log"` to review conversation history
- Check file selection state with `manage_selection action="list"`
- Verify model availability with `list_models`
- Ensure RepoPrompt is running: `claude mcp list`

## Advanced Workflows

### Multi-Stage Development

For complex features spanning multiple components:

1. **Stage 1: Architecture Planning**
   ```
   manage_selection action="replace" paths=["docs/architecture.md", "src/index.ts"]
   chat_send mode="plan" message="Design microservice architecture"
   ```

2. **Stage 2: Core Implementation**
   ```
   manage_selection action="replace" paths=["src/core/*.ts", "src/types/*.ts"]
   chat_send mode="edit" message="Implement core service logic"
   ```

3. **Stage 3: Integration**
   ```
   manage_selection action="add" paths=["src/api/*.ts", "tests/integration/*.ts"]
   chat_send mode="edit" message="Add API endpoints and integration tests"
   ```

### Collaborative Review

Use RepoPrompt for code review and improvements:

```
# Select files for review
manage_selection action="replace" paths=["src/new-feature/*.ts"]

# Set review criteria
set_prompt_state prompt="Review for security vulnerabilities, performance issues, and code style"

# Get detailed analysis
chat_send mode="plan" message="Perform comprehensive code review"
```

## Tips for Success

1. **Let Models Play to Their Strengths**
   - Claude: File navigation, tool use, quick edits
   - O1/O3: Deep reasoning, complex algorithms, architecture
   - Gemini: Large context analysis, pattern recognition

2. **Maintain Clear Separation**
   - Planning: High-level design and approach
   - Execution: Actual code changes
   - Verification: Testing and validation

3. **Use Appropriate Context**
   - Include enough context for understanding
   - But not so much that it overwhelms the model
   - ~60K tokens is often the sweet spot

4. **Iterate Thoughtfully**
   - Start with planning before jumping to implementation
   - Review plans before execution
   - Test incrementally

## Conclusion

RepoPrompt's pair programming mode enables sophisticated AI collaboration for complex development tasks. By leveraging multiple models' strengths and maintaining careful context management, you can tackle challenging problems more effectively than with a single model alone.

Remember: The key is using Claude Code as an intelligent context manager while letting reasoning models handle complex problem-solving. This division of labor produces higher quality results than either model working alone.

---

### Security (`docs/security.md`)

# VibeTunnel Server Security Configuration

## Authentication Options

VibeTunnel Server provides several authentication mechanisms to secure terminal access:

### 1. Standard Authentication

**System User Password** (default)
- Uses the operating system's user authentication
- Validates against local user accounts
- Supports optional SSH key authentication with `--enable-ssh-keys`

**No Authentication Mode**
- Enabled with `--no-auth` flag
- Automatically logs in as the current user
- **WARNING**: Anyone with network access can use the terminal

### 2. Local Bypass Authentication

The `--allow-local-bypass` flag enables a special authentication mode that allows localhost connections to bypass normal authentication requirements.

#### Configuration Options

**Basic Local Bypass**
```bash
vibetunnel-server --allow-local-bypass
```
- Allows any connection from localhost (127.0.0.1, ::1) to access without authentication
- No token required

**Secured Local Bypass**
```bash
vibetunnel-server --allow-local-bypass --local-auth-token <secret-token>
```
- Localhost connections must provide token via `X-VibeTunnel-Local` header
- Adds an additional security layer for local connections

#### Security Implementation

The local bypass feature implements several security checks to prevent spoofing:

1. **IP Address Validation** (`web/src/server/middleware/auth.ts:24-48`)
   - Verifies connection originates from localhost IPs (127.0.0.1, ::1, ::ffff:127.0.0.1)
   - Checks both `req.ip` and `req.socket.remoteAddress`

2. **Header Verification**
   - Ensures no forwarding headers are present (`X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Host`)
   - Prevents proxy spoofing attacks

3. **Hostname Validation**
   - Confirms request hostname is localhost, 127.0.0.1, or [::1]
   - Additional layer of verification

4. **Token Authentication** (when configured)
   - Requires `X-VibeTunnel-Local` header to match configured token
   - Provides shared secret authentication for local tools

#### Security Implications

**Benefits:**
- Enables automated tools and scripts on the same machine to access terminals
- Useful for development workflows and CI/CD pipelines
- Allows local monitoring tools without exposing credentials

**Risks:**
- Any process on the local machine can access terminals (without token)
- Malicious local software could exploit this access
- Token-based mode mitigates but doesn't eliminate local access risks

**Recommended Usage:**
1. **Development Environments**: Safe for local development machines
2. **CI/CD Servers**: Use with token authentication for build scripts
3. **Production Servers**: NOT recommended unless:
   - Combined with token authentication
   - Server has strict local access controls
   - Used only for specific automation needs

#### Example Use Cases

**Local Development Tools**
```javascript
// Local tool accessing VibeTunnel without authentication
const response = await fetch('http://localhost:4020/api/sessions', {
  headers: {
    'X-VibeTunnel-Local': 'my-secret-token' // Only if token configured
  }
});
```

**Automated Testing**
```bash
# Start server with local bypass for tests
vibetunnel-server --allow-local-bypass --local-auth-token test-token

# Test script can now access without password
curl -H "X-VibeTunnel-Local: test-token" http://localhost:4020/api/sessions
```

## Additional Security Considerations

### Network Binding
- Default: Binds to all interfaces (0.0.0.0)
- Use `--bind 127.0.0.1` to restrict to localhost only
- Combine with `--allow-local-bypass` for local-only access

### SSH Key Authentication
- Enable with `--enable-ssh-keys`
- Disable passwords with `--disallow-user-password`
- More secure than password authentication

### HTTPS/TLS
- VibeTunnel does not provide built-in TLS
- Use a reverse proxy (nginx, Caddy) for HTTPS
- Or use secure tunnels (Tailscale, ngrok)

### Best Practices
1. Always use authentication in production
2. Restrict network binding when possible
3. Use token authentication with local bypass
4. Monitor access logs for suspicious activity
5. Keep the server updated for security patches

---

### Spec (`docs/spec.md`)

# VibeTunnel Technical Specification

## Table of Contents

1. [Executive Summary](#executive-summary)
2. [System Architecture](#system-architecture)
3. [Data Flows](#data-flows)
4. [Core Components](#core-components)
5. [Server Implementation](#server-implementation)
6. [Web Frontend](#web-frontend)
7. [iOS Application](#ios-application)
8. [Security Model](#security-model)
9. [Session Management](#session-management)
10. [CLI Integration](#cli-integration)
11. [API Specifications](#api-specifications)
12. [Binary Buffer Protocol](#binary-buffer-protocol)
13. [User Interface](#user-interface)
14. [Configuration System](#configuration-system)
15. [Build and Release](#build-and-release)
16. [Testing Strategy](#testing-strategy)
17. [Performance Requirements](#performance-requirements)
18. [Error Handling](#error-handling)
19. [Update System](#update-system)
20. [Platform Integration](#platform-integration)
21. [Data Formats](#data-formats)

## Executive Summary

### Project Overview

VibeTunnel is a macOS application that provides browser-based access to Mac terminals, designed to make terminal access as simple as opening a web page. The project specifically targets developers and engineers who need to monitor AI agents (like Claude Code) remotely.

### Key Features

- **Zero-Configuration Terminal Access**: Launch terminals with a simple `vt` command
- **Browser-Based Interface**: Access terminals from any modern web browser
- **Real-Time Streaming**: Live terminal updates via WebSocket with binary buffer optimization
- **Session Recording**: Full asciinema format recording support
- **Security Options**: Password protection, localhost-only mode, Tailscale/ngrok integration
- **High-Performance Server**: Node.js server with Bun runtime for optimal JavaScript performance
- **Auto-Updates**: Sparkle framework integration for seamless updates
- **AI Agent Integration**: Special support for Claude Code with shortcuts
- **iOS Companion App**: Mobile terminal access from iPhone/iPad

### Technical Stack

- **Native macOS App**: Swift 6.0, SwiftUI, macOS 14.0+
- **iOS App**: Swift 6.0, SwiftUI, iOS 17.0+
- **Server**: Node.js/TypeScript with Bun runtime
- **Web Frontend**: TypeScript, Lit Web Components, Tailwind CSS
- **Terminal Emulation**: ghostty-web with custom buffer optimization
- **Build System**: Xcode, Swift Package Manager, npm/Bun
- **Distribution**: Signed/notarized DMG with Sparkle updates

## System Architecture

### High-Level Architecture

```
/* Detailed source-code truncated for AI context efficiency. */
```

### Component Interaction Flow

1. **Terminal Launch**: User executes `vt` command
2. **Server Check**: ServerManager ensures Bun server is running
3. **Session Creation**: HTTP POST to create new terminal session
4. **PTY Allocation**: Server allocates pseudo-terminal via node-pty
5. **WebSocket Upgrade**: Client establishes `/ws` connection (v3 framing)
6. **Terminal Transport**: Multiplexed binary frames (`STDOUT` + `SNAPSHOT_VT`)
7. **Recording**: Session data recorded in asciinema format
8. **Session Cleanup**: Resources freed on terminal exit

### Design Principles

- **Single Server Implementation**: One Node.js/Bun server handles everything
- **Protocol-Oriented Swift**: Clean interfaces between macOS components
- **Binary Optimization**: WebSocket v3 framing + VT snapshot v1 for previews/resync
- **Thread Safety**: Swift actors and Node.js event loop for concurrent safety
- **Minimal Dependencies**: Only essential third-party libraries
- **User Privacy**: No telemetry or user tracking

## Data Flows

### Terminal Session Lifecycle
1. User launches the `vt` command or selects **New Session** from the UI.
2. `ServerManager` verifies that the Bun server is running and starts it if needed.
3. A `POST /api/sessions` request triggers `TerminalManager.createTerminal()` on the server.
4. `PtyManager.spawn()` allocates a new PTY process and stores session metadata.
5. The server responds with the session ID and WebSocket URL.
6. Clients connect to `/ws` and `SUBSCRIBE(sessionId, flags)` using WS v3 framing.
7. Terminal output and input are recorded in asciinema format when recording is enabled.
8. On process exit, resources are cleaned up and the client is notified.

### Terminal I/O Flow
1. Keyboard input from the browser or iOS app is sent as WS v3 frames (`INPUT_TEXT` / `INPUT_KEY`).
2. `WsV3Hub` routes input to `PtyManager`.
3. PTY output is tailed and sent back as `STDOUT` frames.
4. Server-side VT snapshots (`SNAPSHOT_VT`) are streamed for previews/resync.
5. The client updates its terminal display accordingly.

### Server Lifecycle Flow
1. Starting the macOS app or running `vt` launches `ServerManager`.
2. `BunServer` spawns the Bun-based HTTP/WebSocket server process.
3. Health checks hit `/api/health` to verify the server is alive.
4. On stop or crash, `ServerManager` gracefully shuts down or restarts the process.

### Remote Access Flow
1. When network mode is enabled, the server binds to `0.0.0.0` for remote access.
2. `NgrokService` or Tailscale can expose a secure public URL.
3. Remote clients reach the server through the tunnel and communicate over HTTPS.

### Authentication Flow
1. Clients request the dashboard or a session endpoint.
2. Basic Auth middleware checks credentials stored via `DashboardKeychain`.
3. Local bypass or token-based headers are honored if configured.
4. Successful authentication allows API and WebSocket communication.

## Core Components

### ServerManager

**Location**: `mac/VibeTunnel/Core/Services/ServerManager.swift`

**Responsibilities**:
- Manages Bun server process lifecycle (start/stop/restart)
- Handles server configuration (port, bind address)
- Provides log streaming from server process
- Coordinates with other services (Ngrok, SessionMonitor)
- Manages server health checks

**Key Methods**:
```swift
func start() async
func stop() async  
func restart() async
func clearAuthCache() async
```

**State Management**:
- Uses `@Observable` for SwiftUI integration
- `@MainActor` ensures UI thread safety
- Publishes server state changes
- Maintains server configuration in UserDefaults

### BunServer

**Location**: `mac/VibeTunnel/Core/Services/BunServer.swift`

**Responsibilities**:
- Spawns and manages the Bun executable process
- Handles process I/O streaming
- Monitors process health and auto-restarts
- Passes configuration via command-line arguments

**Key Features**:
- Embedded vibetunnel binary built with Bun
- Native PTY support via node-pty module
- Automatic crash recovery
- Log streaming to ServerManager

### SessionMonitor

**Location**: `mac/VibeTunnel/Core/Services/SessionMonitor.swift`

**Responsibilities**:
- Polls server for active sessions
- Tracks session lifecycle
- Provides session counts for UI
- Handles session cleanup

**Key Features**:
- Real-time session tracking via polling
- Session metadata caching
- Automatic cleanup detection
- Performance monitoring

### TerminalManager

**Location**: `mac/VibeTunnel/Core/Services/TerminalManager.swift`

**Responsibilities**:
- Integrates with macOS terminal applications
- Handles terminal app selection (Terminal.app, iTerm2, etc.)
- Manages AppleScript execution for terminal launching
- Provides terminal detection utilities

### NgrokService

**Location**: `mac/VibeTunnel/Core/Services/NgrokService.swift`

**Responsibilities**:
- Manages ngrok tunnel lifecycle
- Provides secure public URLs
- Handles authentication token storage
- Monitors tunnel status

**Configuration**:
- API key management via Keychain
- Custom domain support
- Region selection
- Basic auth integration

## Server Implementation

### Node.js/Bun Server

**Location**: `web/src/server/` directory

**Architecture**:
The server is built as a standalone Bun executable that embeds:
- TypeScript server code compiled to JavaScript
- Native node-pty module for PTY support
- Express.js for HTTP handling
- ws library for WebSocket support
- All dependencies bundled into single binary

**Key Components**:
- `server.ts` - HTTP server initialization and lifecycle
- `app.ts` - Express application setup and middleware
- `native/vt-fwd` - External terminal forwarder (Rust, built as `vibetunnel-fwd`)
- `pty/pty-manager.ts` - Native PTY process management
- `pty/session-manager.ts` - Terminal session lifecycle
- `services/terminal-manager.ts` - High-level terminal operations
- `services/ws-v3-hub.ts` - Unified `/ws` WebSocket v3 hub
- `services/cast-output-hub.ts` - Cast tailing → v3 `STDOUT`
- `services/git-status-hub.ts` - Git status updates → v3 `EVENT`
- `routes/sessions.ts` - REST API endpoints

**Server Features**:
- High-performance Bun runtime (3x faster than Node.js)
- Zero-copy buffer operations
- Native PTY handling with proper signal forwarding
- Asciinema recording for all sessions
- WebSocket v3 framing (`/ws`) + VT snapshot v1 for previews/resync
- Graceful shutdown handling

**Build Process**:
```bash
# Build standalone executable
cd web && node build-native.js
# Creates web/native/vibetunnel (60MB Bun executable)
```

## Web Frontend

### Technology Stack

**Location**: `web/src/client/` directory

**Core Technologies**:
- TypeScript for type safety
- Lit Web Components for modern component architecture
- Tailwind CSS for styling
- ghostty-web for terminal rendering
- Unified WebSocket v3 transport (/ws)

### Component Architecture

```
web/src/client/
├── components/
│   ├── app-header.ts        - Application header
│   ├── session-list.ts      - Active session listing
│   ├── session-card.ts      - Individual session display
│   ├── session-view.ts      - Terminal container
│   ├── terminal.ts          - ghostty-web wrapper
│   └── vibe-terminal-buffer.ts - Binary buffer handler
├── services/
│   └── terminal-socket-client.ts - WebSocket v3 transport
├── utils/
│   ├── terminal-renderer.ts - Terminal rendering utilities
│   ├── terminal-preferences.ts - User preferences
│   └── url-highlighter.ts   - URL detection in terminal
└── styles.css               - Tailwind configuration
```

### Key Features

**Dashboard**:
- Real-time session listing with 3-second polling
- One-click terminal creation
- Session metadata display (command, duration, status)
- Responsive grid layout

**Terminal Interface**:
- Full ANSI color support via ghostty-web
- Binary buffer protocol for efficient updates
- Copy/paste functionality
- Responsive terminal sizing
- URL highlighting and click support
- Mobile-friendly touch interactions

**Performance Optimizations**:
- WebSocket v3 framing (`VT` magic, multiplexed sessions)
- Snapshot cadence control (previews vs interactive)
- Asciicast tailing with pruning detection
- WebSocket reconnection and resubscribe logic
- Lazy loading of terminal sessions

## iOS Application

### Overview

**Location**: `ios/VibeTunnel/` directory

**Purpose**: Native iOS companion app for mobile terminal access

### Architecture

**Key Components**:
- `VibeTunnelApp.swift` - Main app entry and lifecycle
- `BufferWebSocketClient.swift` - WebSocket client with binary protocol
- `TerminalView.swift` - Native terminal rendering
- `GhosttyWebView.swift` - Ghostty web renderer (WKWebView)
- `TerminalBufferRenderer.swift` - Buffer snapshot → ANSI conversion
- `SessionService.swift` - Session management API client

### Features

- Native SwiftUI interface
- Server connection management
- Terminal rendering with gesture support
- Session listing and management
- Recording export functionality
- Advanced keyboard support

### Binary Buffer Protocol Support

The iOS app speaks the same terminal transport as the web client:
- Single `/ws` WebSocket (v3 framing)
- Multiplexed sessions (`sessionId` in each frame)
- VT snapshot v1 payloads for previews/resync

## Security Model

### Authentication

**Authentication Modes**:
- System user password authentication (default)
- Optional SSH key authentication (`--enable-ssh-keys`)
- No authentication mode (`--no-auth`)
- Local bypass authentication (`--allow-local-bypass`)

**Local Bypass Security**:
- Allows localhost connections to bypass authentication
- Optional token authentication via `--local-auth-token`
- Implements anti-spoofing checks (IP, headers, hostname)
- See `web/SECURITY.md` for detailed security implications

**Implementation**:
- Main auth middleware: `web/src/server/middleware/auth.ts`
- Local bypass logic: `web/src/server/middleware/auth.ts:24-87`
- Security checks: `web/src/server/middleware/auth.ts:25-48`

### Network Security

**Access Control**:
- Localhost-only mode by default (127.0.0.1)
- Network mode binds to 0.0.0.0
- CORS configuration for web access
- No built-in TLS (use reverse proxy or tunnels)

**Secure Tunneling**:
- Tailscale integration for VPN access
- Ngrok support for secure public URLs
- Both provide TLS encryption
- Authentication handled by tunnel providers

### System Security

**macOS App Privileges**:
- Hardened runtime with specific entitlements
- Allows unsigned executable memory (for Bun)
- Allows DYLD environment variables
- Code signed with Developer ID
- Notarized for Gatekeeper approval

**Data Protection**:
- No persistent storage of terminal content
- Session recordings stored temporarily
- Passwords in Keychain with access control
- No telemetry or analytics

## Session Management

### Session Lifecycle

```
┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐
│ Created │ --> │ Active  │ --> │ Exited  │ --> │ Cleaned │
└─────────┘     └─────────┘     └─────────┘     └─────────┘
```

### Session Model

**TypeScript Definition** (`web/src/server/pty/types.ts`):
```typescript
export interface Session {
  id: string;
  pid: number;
  command: string;
  args: string[];
  cwd: string;
  startTime: number;
  status: 'running' | 'exited';
  exitCode?: number;
  cols: number;
  rows: number;
  recordingPath?: string;
}
```

### Session Operations

**Creation**:
1. Generate unique session ID (UUID)
2. Spawn PTY process with command
3. Initialize asciinema recording
4. Register with SessionManager
5. Return session details to client

**Monitoring**:
- Process exit detection
- Automatic status updates
- Resource usage tracking
- Idle timeout handling (optional)

**Termination**:
- SIGTERM to process group
- PTY cleanup
- Recording finalization
- WebSocket closure notification
- Memory cleanup

## CLI Integration

### vt Command

**Installation**:
The `vt` command is installed as a wrapper script that automatically prepends 'fwd' to commands when using the Bun server.

**Script Location**: `/usr/local/bin/vt`
```bash
#!/bin/bash
# VibeTunnel CLI wrapper for Bun server
exec /usr/local/bin/vibetunnel fwd "$@"
```

### vibetunnel Binary

**Location**: Embedded in app bundle, copied to `/usr/local/bin/vibetunnel`

**Commands**:
- `vibetunnel serve` - Start server (used internally)
- `vibetunnel fwd [command]` - Forward terminal session
- `vibetunnel version` - Show version information

### CLI Features

**Command Parsing**:
- Automatic 'fwd' prepending for vt wrapper
- Shell detection and setup
- Working directory preservation
- Environment variable handling

**Session Creation Flow**:
1. Parse command-line arguments
2. Ensure server is running
3. Create session via API
4. Open browser to session URL
5. Return session information

## API Specifications

### RESTful API

**Base URL**: `http://localhost:4020` (default)

**Authentication**: Optional HTTP Basic Auth

#### Core Endpoints

**GET /api/health**
```json
{
  "status": "ok",
  "version": "1.0.0"
}
```

**GET /api/sessions**
```json
{
  "sessions": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "command": "zsh",
      "args": [],
      "cwd": "/Users/username",
      "startTime": 1704060000000,
      "status": "running",
      "cols": 80,
      "rows": 24
    }
  ]
}
```

**POST /api/sessions**
```json
// Request
{
  "command": ["/bin/zsh", "-l"],
  "workingDir": "/Users/username",
  "name": "optional",
  "cols": 80,
  "rows": 24
}

// Response
{
  "sessionId": "550e8400-e29b-41d4-a716-446655440000",
  "createdAt": "2025-12-19T08:00:00.000Z"
}
```

**DELETE /api/sessions/:id**
```json
{
  "success": true
}
```

**GET /api/sessions/:id/snapshot**
Returns current terminal buffer state for initial render

**POST /api/sessions/:id/input**
Send keyboard input to terminal

**POST /api/sessions/:id/resize**
```json
{
  "cols": 120,
  "rows": 40
}
```

### WebSocket Protocol

**Endpoint**: `GET /ws` (WebSocket upgrade)

Terminal transport uses binary WebSocket v3 framing (multiplexed sessions).

Details: `docs/websocket.md`.

## Binary Buffer Protocol

### Overview

Terminal transport is a binary protocol layered on a single WebSocket (`/ws`).
It multiplexes sessions and supports both:
- live PTY output (`STDOUT`)
- server-rendered previews / hard resync (`SNAPSHOT_VT`, VT snapshot v1)

### Message Format

See `docs/websocket.md` (frame layout + message types).

### Protocol Flow

1. Client connects to `/ws` (one socket).
2. Client sends `SUBSCRIBE(sessionId, flags)` for each interested session.
3. Server streams `STDOUT`/`SNAPSHOT_VT`/`EVENT` frames per subscription.
4. Client sends `INPUT_TEXT`/`INPUT_KEY`/`RESIZE` frames back.

### Implementation Details

**Server**:
- `web/src/server/services/ws-v3-hub.ts` (frame routing + subscriptions)
- `web/src/server/services/cast-output-hub.ts` (tails cast → `STDOUT`)
- `web/src/server/services/terminal-manager.ts` (VT snapshot v1 → `SNAPSHOT_VT`)

**Web Client** (`web/src/client/components/vibe-terminal-buffer.ts`):
- Consumes `STDOUT` and `SNAPSHOT_VT` via `web/src/client/services/terminal-socket-client.ts`

**iOS Client** (`ios/VibeTunnel/Services/BufferWebSocketClient.swift`):
- Same `/ws` v3 framing + VT snapshot v1 decoding

## User Interface

### Menu Bar Application

**Components**:
- Status icon indicating server state
- Quick access menu
- Session count display
- Settings access
- About/Help options

**State Indicators**:
- Gray: Server stopped
- Green: Server running
- Red: Error state
- Animated: Starting/stopping

### Settings Window

**General Tab**:
- Server port configuration
- Launch at login toggle
- Show in Dock option
- Update channel selection

**Dashboard Tab**:
- Access mode (localhost/network)
- Password protection toggle
- Authentication settings
- Dashboard URL display

**Advanced Tab**:
- Cleanup on startup
- CLI tools installation
- Server console access
- Debug logging

**Debug Tab** (hidden by default):
- Server type display (Bun only)
- Console log viewer
- Diagnostic information

## Configuration System

### User Defaults

**Storage**: `UserDefaults.standard`

**Key Settings**:
```swift
serverPort: String = "4020"
dashboardAccessMode: String = "localhost"
dashboardPasswordEnabled: Bool = false
launchAtLogin: Bool = false
showDockIcon: Bool = false
cleanupOnStartup: Bool = true
```

### Keychain Integration

**DashboardKeychain Service**:
- Stores dashboard password securely
- Uses kSecClassInternetPassword
- Server and port-specific storage
- Handles password updates/deletion

### Configuration Flow

1. **App Launch**: Load settings from UserDefaults
2. **Server Start**: Pass configuration via CLI arguments
3. **Runtime Changes**: Update server without restart where possible
4. **Password Changes**: Clear server auth cache

## Build and Release

### Build System

**Requirements**:
- Xcode 16.0+
- macOS 14.0+ SDK
- Node.js 22.12 through 24.x
- Bun runtime
- Rustup; the forwarder toolchain is pinned by `native/vt-fwd/rust-toolchain.toml`

**Build Process**:
```bash
# Complete build
cd mac && ./scripts/build.sh --configuration Release --sign

# Development build (with Poltergeist if available)
poltergeist  # Automatic rebuilds on file changes

# Or manual build
cd mac && xcodebuild -project VibeTunnel.xcodeproj -scheme VibeTunnel -configuration Debug build
```

**Build Phases**:
1. Build Bun executable from web sources
2. Compile Swift application
3. Copy resources (Bun binary, web assets)
4. Code sign application
5. Create DMG for distribution

### Code Signing

**Entitlements** (`mac/VibeTunnel/VibeTunnel.entitlements`):
```xml
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
```

### Distribution

**Release Process**:
1. Build and sign application
2. Create notarized DMG
3. Generate Sparkle appcast
4. Upload to GitHub releases
5. Update appcast XML

**Package Contents**:
- ARM64-only binary (Apple Silicon required)
- Embedded Bun server executable
- Web assets and resources
- Sparkle update framework

## Testing Strategy

### macOS Tests

**Framework**: Swift Testing (Swift 6)

**Test Organization**:
```
mac/VibeTunnelTests/
├── ServerManagerTests.swift
├── SessionMonitorTests.swift
├── TerminalManagerTests.swift
├── DashboardKeychainTests.swift
├── CLIInstallerTests.swift
├── NetworkUtilityTests.swift
└── Utilities/
    ├── TestTags.swift
    ├── TestFixtures.swift
    └── MockHTTPClient.swift
```

**Test Tags**:
- `.critical` - Core functionality
- `.networking` - Network operations
- `.concurrency` - Async operations
- `.security` - Security features

### Node.js Tests

**Framework**: Vitest

**Test Structure**:
```
web/src/test/
├── e2e/
│   ├── hq-mode.e2e.test.ts
│   └── server-smoke.e2e.test.ts
├── setup.ts
└── test-utils.ts
```

**Coverage Requirements**:
- 80% line coverage
- 80% function coverage
- 80% branch coverage

## Performance Requirements

### Latency Targets

**Terminal I/O**:
- Keystroke to display: < 50ms
- Binary buffer update: < 100ms
- WebSocket ping/pong: < 10ms

**API Response Times**:
- Session list: < 50ms
- Session creation: < 200ms
- Health check: < 10ms

### Resource Usage

**Memory**:
- macOS app idle: < 50MB
- Bun server idle: < 100MB
- Per session: < 10MB
- Buffer cache: 64KB per session

**CPU**:
- Idle: < 1%
- Active session: < 5%
- Multiple sessions: Linear scaling

### Scalability

**Concurrent Sessions**:
- Target: 50 simultaneous sessions
- Tested: 100+ sessions
- Graceful degradation
- Buffer pooling for efficiency

## Error Handling

### Error Categories

**User Errors**:
- Port already in use
- Invalid configuration
- Authentication failures
- Permission denied

**System Errors**:
- Server crash/restart
- PTY allocation failures
- Process spawn errors
- WebSocket disconnections

### Error Recovery

**Server Crashes**:
- Automatic restart by ServerManager
- Session state preserved in memory
- Client reconnection supported
- Graceful degradation

**Client Disconnections**:
- WebSocket auto-reconnect
- Exponential backoff
- Session state preserved
- Buffer replay on reconnect

## Update System

### Sparkle Integration

**Configuration**:
- Update check interval: 24 hours
- Automatic download in background
- User prompt for installation
- Delta updates supported

**Update Channels**:
- Stable: Production releases
- Pre-release: Beta testing

### Update Process

1. Check appcast.xml for updates
2. Download update package
3. Verify EdDSA signature
4. Prompt user for installation
5. Install and restart application

## Platform Integration

### macOS Integration

**System Features**:
- Launch at login via SMAppService
- Menu bar and Dock modes
- Notification Center support
- Keyboard shortcuts
- AppleScript support

### Terminal Integration

**Supported Terminals**:
- Terminal.app (default)
- iTerm2
- Warp
- Alacritty
- Hyper
- kitty

**Detection Method**:
- Check bundle identifiers
- Verify app existence
- User preference storage

## Data Formats

### Asciinema Recording

**Format**: Asciinema v2

**Header**:
```json
{
  "version": 2,
  "width": 80,
  "height": 24,
  "timestamp": 1704060000,
  "command": "/bin/zsh",
  "title": "VibeTunnel Session"
}
```

**Events**: Newline-delimited JSON
```
[0.123456, "o", "terminal output"]
[0.234567, "i", "keyboard input"]
```

### Session Storage

Sessions are ephemeral and exist only in server memory. Recordings are stored temporarily in the system temp directory and cleaned up after 24 hours or on server restart with cleanup enabled.

## Conclusion

VibeTunnel achieves its goal of simple, secure terminal access through a carefully architected system combining native macOS development with modern web technologies. The single Node.js/Bun server implementation provides excellent performance while maintaining simplicity.

The binary buffer protocol ensures efficient terminal streaming, while the clean architectural boundaries enable independent evolution of components. With careful attention to macOS platform conventions and user expectations, VibeTunnel delivers a professional-grade solution for terminal access needs.
This specification serves as the authoritative reference for understanding, maintaining, and extending the VibeTunnel project.

---

### Tailscale Ios (`docs/tailscale_ios.md`)

# VibeTunnel Architecture Deep Dive

## 🏗️ High-Level Architecture

VibeTunnel is a sophisticated terminal multiplexer ecosystem with native macOS/iOS apps and a powerful web interface. Here's the complete architectural breakdown:

```
/* Detailed source-code truncated for AI context efficiency. */
```

## 🎯 Core Components Breakdown

### 1. **macOS Application (Swift/SwiftUI)**

The native macOS app serves as the system orchestrator:

```
mac/VibeTunnel/
├── Core/
│   ├── Services/
│   │   ├── ServerManager.swift      # Central orchestrator
│   │   ├── BunServer.swift          # Bun runtime integration
│   │   ├── SessionMonitor.swift     # Session tracking
│   │   ├── TTYForwardManager.swift  # Terminal forwarding
│   │   ├── UnixSocketConnection.swift # IPC communication
│   │   └── TailscaleServeService.swift # Remote access
│   ├── Models/
│   │   ├── TunnelSession.swift      # Session data model
│   │   └── AppConstants.swift       # Configuration
│   └── Protocols/
│       └── VibeTunnelServer.swift   # Server interface
└── Presentation/
    ├── Views/                       # SwiftUI views
    └── Components/                  # UI components
```

**Key Responsibilities:**
- **Process Management**: Spawns and monitors the Bun/Node.js server
- **Log Aggregation**: Captures all logs from server and frontend
- **System Integration**: Menu bar UI, notifications, keychain
- **Remote Access**: Tailscale/Ngrok tunnel management

### 2. **Web Server (Node.js/Bun)**

The TypeScript server handles all terminal operations:

```
web/src/server/
├── server.ts                 # Main server entry (912 lines)
├── pty/
│   ├── pty-manager.ts       # Native PTY management
│   ├── session-manager.ts   # Session lifecycle
│   └── types.ts            # TypeScript definitions
├── services/
│   ├── terminal-manager.ts  # High-level terminal ops
│   ├── buffer-aggregator.ts # Binary buffer protocol
│   ├── auth-service.ts     # SSH key authentication
│   ├── activity-monitor.ts # Resource tracking
│   └── hq-client.ts        # Multi-server mode
├── routes/
│   ├── sessions.ts         # Session REST API
│   ├── websocket-input.ts  # WebSocket handlers
│   ├── auth.ts            # Authentication endpoints
│   └── control.ts         # Unix socket control
└── middleware/
    └── auth.ts            # JWT validation
```

**Key Features:**
- **Session Management**: Full PTY lifecycle (create/resize/kill)
- **Binary Buffer Protocol**: Optimized terminal streaming
- **Authentication**: JWT + SSH keys + PAM
- **Distributed Mode**: HQ server for multi-machine setups

### 3. **iOS Application (Swift/SwiftUI)**

Mobile terminal client with full feature parity:

```
ios/VibeTunnel/
├── Services/
│   ├── BufferWebSocketClient.swift  # Binary protocol client
│   ├── SessionService.swift         # Session management
│   ├── ConnectionManager.swift      # Server connections
│   └── BonjourDiscoveryService.swift # Local discovery
├── Views/
│   ├── Terminal/
│   │   ├── TerminalView.swift      # Main terminal UI
│   │   ├── XtermWebView.swift      # xterm.js wrapper
│   │   └── TerminalHostingView.swift # UIKit bridge
│   └── Sessions/
│       └── SessionListView.swift    # Session browser
└── Models/
    ├── TerminalSnapshot.swift       # Buffer state
    └── Session.swift                # Session model
```

### 4. **Web Frontend (TypeScript/LitElement)**

Browser-based terminal interface:

```
web/src/client/
├── app.ts                   # Main LitElement app
├── components/
│   ├── terminal.ts         # xterm.js wrapper
│   ├── session-list.ts     # Session management
│   └── file-browser.ts     # File navigation
└── services/
    ├── websocket.ts        # WebSocket client
    └── api-client.ts       # REST client
```

## 📡 Communication Protocols

### **1. Binary Buffer Protocol (0xBF Magic Byte)**

Optimized terminal streaming protocol:

```typescript
// Message Format
[Magic Byte: 0xBF] [Type: 1 byte] [Length: 4 bytes] [Payload: N bytes]

// Types:
0x01: Full buffer snapshot
0x02: Delta update
0x03: Cursor position
0x04: Terminal resize
```

**Flow:**
```
Terminal Output → BufferAggregator → Binary Encode → WebSocket → Client Decode → xterm.js
```

### **2. REST API Endpoints**

```
POST   /api/sessions              # Create session
GET    /api/sessions              # List sessions
DELETE /api/sessions/:id          # Kill session
POST   /api/sessions/:id/resize   # Resize terminal
WS     /api/sessions/:id/ws       # Terminal I/O stream
GET    /api/auth/challenge        # SSH key challenge
POST   /api/auth/ssh-key          # SSH key verify
```

### **3. Unix Socket IPC**

Mac app ↔ Server communication:

```swift
// Control Protocol
{
  "type": "session.create",
  "payload": {
    "cols": 80,
    "rows": 24,
    "cwd": "/Users/chris"
  }
}
```

## 🔄 Key Data Flows

### **Session Creation Flow**

```
User Request
    │
    ▼
[macOS App] ServerManager.createSession()
    │
    ├─→ [IPC] Unix Socket Message
    │
    ▼
[Server] POST /api/sessions
    │
    ├─→ TerminalManager.createTerminal()
    ├─→ PtyManager.spawn() → node-pty
    ├─→ Create ~/.vibetunnel/control/[sessionId]/
    ├─→ Start BufferAggregator
    │
    ▼
[Response] { sessionId, wsUrl }
    │
    ▼
[Client] Connect WebSocket
    │
    ▼
[Bidirectional Terminal I/O]
```

### **Log Aggregation Pipeline**

```
┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Frontend   │────▶│   Server     │────▶│   Mac App    │────▶│  macOS Log   │
│  console.log │HTTP │ [CLIENT:*]   │Pipe │ ServerOutput │     │  Unified     │
└──────────────┘     └──────────────┘     └──────────────┘     └──────────────┘
                         
[component] msg → POST /api/logs → [CLIENT:component] → Logger category → vtlog
```

## 🔐 Security Architecture

### **Authentication Layers**

```
┌─────────────────────────────────────────────────────┐
│                  Client Request                      │
└────────────────────────┬────────────────────────────┘
                         ▼
┌─────────────────────────────────────────────────────┐
│              Authentication Middleware               │
├─────────────────────────────────────────────────────┤
│  1. Local Bypass (localhost + token)                │
│  2. JWT Token (from previous auth)                  │
│  3. SSH Key Challenge/Response                      │
│  4. Password (PAM or env var)                       │
│  5. Bearer Token (HQ mode)                          │
└─────────────────────────────────────────────────────┘
                         │
                    Authenticated
                         ▼
┌─────────────────────────────────────────────────────┐
│                  Protected Routes                    │
└─────────────────────────────────────────────────────┘
```

## 🚀 Advanced Features

### **1. Distributed Mode (HQ)**

```
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  Machine A   │────▶│  HQ Server   │◀────│  Machine B   │
│  (Remote)    │     │  (Central)   │     │  (Remote)    │
└──────────────┘     └──────────────┘     └──────────────┘
     
Registration → Session Discovery → Proxied Access
```

### **2. Remote Access (Tailscale/Ngrok)**

```
Internet → Tailscale Funnel/Ngrok → localhost:4020 → VibeTunnel
            ├─ HTTPS termination
            ├─ Authentication
            └─ Traffic routing
```

### **3. Activity Monitoring**

```typescript
// Idle detection and resource management
ActivityMonitor → Session idle > 5min → Mark inactive
                → System resources → Auto-cleanup
                → WebSocket ping/pong → Connection health
```

## 📁 File System Structure

```
~/.vibetunnel/
├── control/                      # Session control files
│   └── [sessionId]/
│       ├── session.json         # Metadata
│       ├── stdout              # Output log
│       ├── stdin               # Input log
│       ├── activity.json       # Activity status
│       └── ipc.sock           # Unix socket
├── logs/                        # Application logs
├── keys/                        # VAPID keys
└── config/                      # Server config
```

## 🎨 Technology Stack

- **macOS/iOS**: Swift 6, SwiftUI, Combine, os.log
- **Server**: Node.js/Bun, TypeScript, Express, node-pty
- **Frontend**: TypeScript, LitElement, xterm.js, Web Components
- **Protocols**: WebSocket, Unix Sockets, REST, Binary Buffer
- **Security**: JWT, Ed25519 SSH keys, PAM, Keychain
- **Build**: Xcode, Swift Package Manager, pnpm, esbuild

## 🔧 Key Implementation Details

### **Server Lifecycle**

1. **Startup**: Mac app spawns Bun process with embedded server
2. **Health Check**: Polls /health endpoint until ready
3. **Operation**: Handles sessions, forwards logs to Mac app
4. **Shutdown**: Graceful termination on SIGTERM

### **Session Persistence**

- Sessions survive server restarts via control directory
- Reconnection supported through session ID
- Automatic cleanup of orphaned sessions

### **Performance Optimizations**

- **Binary Protocol**: 10x smaller than JSON for terminal data
- **Buffer Aggregation**: Batches updates to reduce WebSocket messages
- **Delta Updates**: Only sends changes, not full buffer
- **Lazy Loading**: Sessions load on-demand

### **Development vs Production**

- **Development**: Hot reload, verbose logging, dev server mode
- **Production**: Embedded server, optimized builds, minimal logging
- **No Backwards Compatibility**: Everything ships together as one unit

---

### Testing (`docs/testing.md`)

# Testing

Short, reliable gate + forwarder E2E.

## Full Gate (required before handoff)

```bash
./scripts/validate-docs.sh

(
  cd native/vt-fwd
  cargo fmt --all -- --check
  cargo clippy --all-targets --all-features --locked -- -D warnings
  cargo test --all-targets --all-features --locked
  cargo build --release --locked
  python3 test/e2e.py target/release/vibetunnel-fwd
)

(
  cd web
  pnpm run build
  pnpm run check
  pnpm run test:ci
)
```

Notes:
- `web/scripts/check-all.sh` runs format:check, lint, lint:typeaware, typecheck, test:vt in parallel.
- `pnpm run test:ci` runs the Vitest suites once with the verbose reporter.

## Rust Formatting + Linting

Rustup installs the pinned formatter and linter components from `native/vt-fwd/rust-toolchain.toml`.

```bash
cd native/vt-fwd
cargo fmt --all -- --check
cargo clippy --all-targets --all-features --locked -- -D warnings
cargo test --all-targets --all-features --locked
```

## Rust Forwarder E2E (random binary → disk artifacts)

Goal: verify `session.json`, `stdout`, `stdin` FIFO, `ipc.sock` exist; stdout has bytes; session exits cleanly.

```bash
cd native/vt-fwd
cargo build --release --locked
python3 test/e2e.py target/release/vibetunnel-fwd
```

The Unix-only Python 3 test covers exit propagation, private artifact permissions, random binary output, valid cast JSON, fragmented and oversized IPC frames, heartbeat, resize, stdin, title updates, malformed signals, and process-group termination.

Manual artifact inspection requires a C compiler:

```bash
cat > /tmp/vt-randout.c <<'EOF'
#include <fcntl.h>
#include <unistd.h>
int main(void){int fd=open("/dev/urandom",O_RDONLY);unsigned char b[256];
ssize_t n=read(fd,b,sizeof(b)); if(n>0) write(1,b,(size_t)n);
write(1,"\nDONE\n",6); sleep(1); return 0;}
EOF
cc /tmp/vt-randout.c -o /tmp/vt-randout

SESSION_ID="rusttest_$(date +%s)"
CONTROL_DIR="$HOME/.vibetunnel/control/$SESSION_ID"

native/vt-fwd/target/release/vibetunnel-fwd --session-id "$SESSION_ID" /tmp/vt-randout > /dev/null &
FWD_PID=$!

# wait for artifacts
for i in {1..50}; do [ -f "$CONTROL_DIR/session.json" ] && break; sleep 0.1; done

ls -la "$CONTROL_DIR"

# artifact checks
test -f "$CONTROL_DIR/session.json"
test -f "$CONTROL_DIR/stdout"
test -p "$CONTROL_DIR/stdin"
test -S "$CONTROL_DIR/ipc.sock"

wait $FWD_PID

# post-exit checks
wc -c "$CONTROL_DIR/stdout"
tail -n 1 "$CONTROL_DIR/session.json"
```

Expectations:
- `stdout` > 0 bytes.
- `session.json` ends with `}` and includes `status: "exited"` + `exitCode`.

## Web E2E (Playwright)

```bash
cd web
pnpm run test:e2e
```

Optional variants:
```bash
pnpm run test:e2e:headed
pnpm run test:e2e:debug
```

## macOS Tests

```bash
cd mac
./scripts/lint.sh
xcodebuild test
```

## iOS Tests

```bash
cd ios
./scripts/test-with-coverage.sh
```

## Cleanup

```bash
rm -rf "$HOME/.vibetunnel/control/rusttest_*"
```

---

### Unified Notifications Test Plan (`docs/unified-notifications-test-plan.md`)

# Unified Notification System Test Plan

## Overview
Test the new unified notification system that sends all notifications from the server to Mac via Unix socket.

## Architecture Changes
- Server SessionMonitor detects all notification events
- Events sent to Mac via Unix socket (session-monitor category)
- Mac NotificationControlHandler processes and displays notifications
- No more polling or duplicate detection logic

## Test Scenarios

### 1. Bell Notification Test
```bash
# In any VibeTunnel session
echo -e '\a'
# or
printf '\007'
```
**Expected**: Bell notification appears on both Mac and Web

### 2. Command Completion Test (>3 seconds)
```bash
# Run a command that takes more than 3 seconds
sleep 4
# or
find / -name "*.txt" 2>/dev/null | head -100
```
**Expected**: Command completion notification after command finishes

### 3. Command Error Test
```bash
# Run a command that fails
ls /nonexistent/directory
# or
false
```
**Expected**: Command error notification with exit code

### 4. Session Start/Exit Test
```bash
# From another terminal or web UI
# Create new session
# Exit session with 'exit' command
```
**Expected**: Session start and exit notifications

## Verification Steps

1. **Enable all notifications in Mac Settings**:
   - Open VibeTunnel → Settings → Notifications
   - Enable "Show Session Notifications" 
   - Enable all notification types
   - Enable sound if desired

2. **Monitor Unix socket traffic** (optional):
   ```bash
   # In a separate terminal, monitor the control socket
   sudo dtrace -n 'syscall::write:entry /execname == "VibeTunnel" || execname == "node"/ { printf("%d: %s", pid, copyinstr(arg1, 200)); }'
   ```

3. **Check logs**:
   ```bash
   # Monitor VibeTunnel logs
   ./scripts/vtlog.sh -f -c NotificationControl
   
   # Check for session-monitor events
   ./scripts/vtlog.sh -f | grep "session-monitor"
   ```

## Success Criteria

1. ✅ All notification types work on Mac via Unix socket
2. ✅ No duplicate notifications
3. ✅ Notifications respect user preferences (on/off toggles)
4. ✅ No more 3-second polling from Mac SessionMonitor
5. ✅ Single source of truth (server) for all notification events

## Troubleshooting

- If no notifications appear, check:
  - Mac app is connected to server (check Unix socket connection)
  - Notifications are enabled in settings
  - Check vtlog for any errors
  
- If notifications are delayed:
  - Check if bell detection is working (should be instant)
  
- If getting duplicate notifications:
  - Ensure only one VibeTunnel instance is running
  - Check that old SessionMonitor code is not running

---

### Websocket (`docs/websocket.md`)

# WebSocket v3 (Unified Ghostty Transport)

Single WebSocket. Multiplexed sessions. Binary framing. Same protocol for web + iOS.

## Endpoint
- `GET /ws` (WebSocket upgrade)
- Auth:
  - normal: `?token=...`
  - `--no-auth`: token optional

## Framing (binary)
```
u16  magic = 0x5654   // "VT" LE
u8   version = 3
u8   type
u32  sessionIdLenLE
u8[] sessionId (UTF-8, may be empty)
u32  payloadLenLE
u8[] payload
```
Source of truth: `web/src/shared/ws-v3.ts`.

## Message types (v3)
IDs: `WsV3MessageType` in `web/src/shared/ws-v3.ts`.

Client → Server:
- `SUBSCRIBE` payload = `encodeWsV3SubscribePayload({ flags, snapshotMinIntervalMs, snapshotMaxIntervalMs })`
  - `sessionId` may be empty (`""`) to subscribe to global `EVENT` frames (no per-session STDOUT/snapshots).
- `UNSUBSCRIBE` payload empty
- `INPUT_TEXT` payload = UTF-8 text bytes (includes escape sequences when needed)
- `INPUT_KEY` payload = UTF-8 key name (`SpecialKey`)
- `RESIZE` payload = `u32 colsLE` + `u32 rowsLE`
- `KILL` payload = UTF-8 signal (default `SIGTERM`)
- `RESET_SIZE` payload empty
- `PING` payload optional

Server → Client:
- `WELCOME` payload = JSON `{ ok: true, version: 3 }`
- `STDOUT` payload = UTF-8 bytes from PTY (asciinema “o” frames’ data)
- `SNAPSHOT_VT` payload = VT snapshot bytes (see next section)
- `EVENT` payload = JSON
  - per-session: `exit`, `git-status-update`, …
  - global (`sessionId == ""`): `connected`, `test-notification`, …
- `ERROR` payload = JSON `{ message: string }`
- `PONG` payload optional

## Subscribe flags
`WsV3SubscribeFlags` in `web/src/shared/ws-v3.ts`:
- `Stdout` (bit 0)
- `Snapshots` (bit 1)
- `Events` (bit 2)

## Snapshot payload (`SNAPSHOT_VT`)
Payload is the existing **VT snapshot v1** byte format (magic `VT`, version `1`).
- Used for:
  - session list previews/thumbnails (server-rendered)
  - optional “hard resync” for interactive clients
- Encoder: `TerminalManager` (server-side Ghostty emulation)

## Implementation map
- Server hub: `web/src/server/services/ws-v3-hub.ts`
- Stdout source: `web/src/server/services/cast-output-hub.ts` (tails cast + pruning via `lastClearOffset`)
- Git events: `web/src/server/services/git-status-hub.ts`
- Web client transport: `web/src/client/services/terminal-socket-client.ts`
- iOS transport: `ios/VibeTunnel/Services/BufferWebSocketClient.swift`

## HQ mode
HQ uses the same `/ws` v3 frames.
- HQ keeps one upstream WS per remote.
- Downstream subscriptions aggregate flags per session and fan out frames to clients.

## Removed legacy transports
- `/buffers` (v2 `0xBF` framing)
- `/ws/input`

Still available:
- `/api/sessions/:id/text` (plain-text rendering of the current terminal buffer)

---

### Worktree Spec (`docs/worktree-spec.md`)

# Git Worktree Implementation Specification

This document describes the technical implementation of Git worktree support in VibeTunnel.

## Architecture Overview

VibeTunnel's worktree support is built on three main components:

1. **Backend API** - Git operations and worktree management
2. **Frontend UI** - Session creation and worktree visualization  
3. **Git Hooks** - Automatic synchronization and follow mode

## Backend Implementation

### Core Services

**GitService** (`web/src/server/services/git-service.ts`)
- Not implemented as a service, Git operations are embedded in routes
- Client-side GitService exists at `web/src/client/services/git-service.ts`

**Worktree Routes** (`web/src/server/routes/worktrees.ts`)
- `GET /api/worktrees` - List all worktrees with stats and follow mode status
- `POST /api/worktrees` - Create new worktree
- `DELETE /api/worktrees/:branch` - Remove worktree
- `POST /api/worktrees/switch` - Switch branch and enable follow mode
- `POST /api/worktrees/follow` - Enable/disable follow mode for a branch

**Git Routes** (`web/src/server/routes/git.ts`)
- `GET /api/git/repo-info` - Get repository information
- `POST /api/git/event` - Process git hook events (internal use)
- `GET /api/git/follow` - Check follow mode status for a repository
- `GET /api/git/notifications` - Get pending notifications

### Key Functions

```typescript
// List worktrees with extended information
async function listWorktreesWithStats(repoPath: string): Promise<Worktree[]>

// Create worktree with automatic path generation
async function createWorktree(
  repoPath: string, 
  branch: string, 
  path: string, 
  baseBranch?: string
): Promise<void>

// Handle branch switching with safety checks
async function switchBranch(
  repoPath: string, 
  branch: string
): Promise<void>
```

### Git Operations

All Git operations use Node.js `child_process.execFile` for security:

```typescript
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';

const execFileAsync = promisify(execFile);

// Execute git commands safely
async function execGit(args: string[], options?: { cwd?: string }) {
  return execFileAsync('git', args, {
    ...options,
    timeout: 30000,
    maxBuffer: 10 * 1024 * 1024, // 10MB
  });
}
```

### Follow Mode Implementation

Follow mode uses Git hooks and git config for state management:

1. **State Storage**: Git config `vibetunnel.followWorktree`
   ```bash
   # Follow mode stores the worktree path in main repository
   git config vibetunnel.followWorktree "/path/to/worktree"
   
   # Check follow mode status
   git config vibetunnel.followWorktree
   
   # Disable follow mode
   git config --unset vibetunnel.followWorktree
   ```

2. **Git Hooks**: Installed in BOTH main repo and worktree
   - `post-checkout`: Detects branch switches
   - `post-commit`: Detects new commits
   - `post-merge`: Detects merge operations
   
3. **Event Processing**: Hooks execute `vt git event` command
4. **Synchronization Logic**:
   - Worktree events → Main repo syncs (branch, commits, checkouts)
   - Main repo commits → Worktree syncs (commits only)
   - Main repo branch switch → Auto-unfollow

## Frontend Implementation

### Components

**SessionCreateForm** (`web/src/client/components/session-create-form.ts`)
- Branch/worktree selection UI
- Smart branch switching logic
- Warning displays for conflicts

**WorktreeManager** (`web/src/client/components/worktree-manager.ts`)
- Dedicated worktree management UI
- Follow mode controls
- Worktree deletion and branch switching
- **Note**: Does not include UI for creating new worktrees

### State Management

```typescript
// Session creation state
@state() private currentBranch: string = '';
@state() private selectedBaseBranch: string = '';
@state() private selectedWorktree?: string;
@state() private availableWorktrees: Worktree[] = [];

// Branch switching state  
@state() private branchSwitchWarning?: string;
@state() private isLoadingBranches = false;
@state() private isLoadingWorktrees = false;
```

### Branch Selection Logic

The new session dialog implements smart branch handling:

1. **No Worktree Selected**:
   ```typescript
   if (selectedBaseBranch !== currentBranch) {
     try {
       await gitService.switchBranch(repoPath, selectedBaseBranch);
       effectiveBranch = selectedBaseBranch;
     } catch (error) {
       // Show warning, use current branch
       this.branchSwitchWarning = "Cannot switch due to uncommitted changes";
       effectiveBranch = currentBranch;
     }
   }
   ```

2. **Worktree Selected**:
   ```typescript
   // Use worktree's path and branch
   effectiveWorkingDir = worktreeInfo.path;
   effectiveBranch = selectedWorktree;
   // No branch switching occurs
   ```

### UI Updates

Dynamic labels based on context:
```typescript
${this.selectedWorktree ? 'Base Branch for Worktree:' : 'Switch to Branch:'}
```

Help text explaining behavior:
```typescript
${this.selectedWorktree
  ? 'New worktree branch will be created from this branch'
  : this.selectedBaseBranch !== this.currentBranch
    ? `Session will start on ${this.selectedBaseBranch} (currently on ${this.currentBranch})`
    : `Current branch: ${this.currentBranch}`
}
```

## Git Hook Integration

### Hook Installation

Automatic hook installation on repository access:

```typescript
// Install hooks when checking Git repository
async function installGitHooks(repoPath: string): Promise<void> {
  const hooks = ['post-commit', 'post-checkout'];
  for (const hook of hooks) {
    await installHook(repoPath, hook);
  }
}
```

### Hook Script

The hook implementation uses the `vt` command:

```bash
#!/bin/sh
# VibeTunnel Git hook - post-checkout
# This hook notifies VibeTunnel when Git events occur

# Check if vt command is available
if command -v vt >/dev/null 2>&1; then
  # Run in background to avoid blocking Git operations
  vt git event &
fi

# Always exit successfully
exit 0
```

The `vt git event` command:
- Sends the repository path to the server via `POST /api/git/event`
- Server determines what changed by examining current git state
- Triggers branch synchronization if follow mode is enabled
- Sends notifications to connected sessions
- Runs in background to avoid blocking git operations

### Follow Mode Logic

The git event handler determines sync behavior based on event source:

```typescript
// Get follow mode configuration
const followWorktree = await getGitConfig(mainRepoPath, 'vibetunnel.followWorktree');
if (!followWorktree) return; // Follow mode not enabled

// Determine if event is from main repo or worktree
const eventPath = req.body.repoPath;
const isFromWorktree = eventPath === followWorktree;
const isFromMain = eventPath === mainRepoPath;

if (isFromWorktree) {
  // Worktree → Main sync
  switch (event) {
    case 'checkout':
      // Sync branch or commit to main
      const target = req.body.branch || req.body.commit;
      await execGit(['checkout', target], { cwd: mainRepoPath });
      break;
    
    case 'commit':
    case 'merge':
      // Pull changes to main
      await execGit(['fetch'], { cwd: mainRepoPath });
      await execGit(['merge', 'FETCH_HEAD'], { cwd: mainRepoPath });
      break;
  }
} else if (isFromMain) {
  // Main → Worktree sync
  switch (event) {
    case 'checkout':
      // Branch switch in main = stop following
      await unsetGitConfig(mainRepoPath, 'vibetunnel.followWorktree');
      sendNotification('Follow mode disabled - switched branches in main repository');
      break;
    
    case 'commit':
      // Sync commit to worktree
      await execGit(['fetch'], { cwd: followWorktree });
      await execGit(['merge', 'FETCH_HEAD'], { cwd: followWorktree });
      break;
  }
}
```

## Data Models

### Worktree

The Worktree interface differs between backend and frontend:

**Backend** (`web/src/server/routes/worktrees.ts`):
```typescript
interface Worktree {
  path: string;
  branch: string;
  HEAD: string;
  detached: boolean;
  prunable?: boolean;
  locked?: boolean;
  lockedReason?: string;
  // Extended stats
  commitsAhead?: number;
  filesChanged?: number;
  insertions?: number;
  deletions?: number;
  hasUncommittedChanges?: boolean;
}
```

**Frontend** (`web/src/client/services/git-service.ts`):
```typescript
interface Worktree extends BackendWorktree {
  // UI helpers - added dynamically by routes
  isMainWorktree?: boolean;
  isCurrentWorktree?: boolean;
}
```

The UI helper fields are computed dynamically in the worktree routes based on the current repository path and are not stored in the backend data model.

### Session with Git Info

```typescript
interface Session {
  id: string;
  name: string;
  command: string[];
  workingDir: string;
  // Git information (from shared/types.ts)
  gitRepoPath?: string;
  gitBranch?: string;
  gitAheadCount?: number;
  gitBehindCount?: number;
  gitHasChanges?: boolean;
  gitIsWorktree?: boolean;
  gitMainRepoPath?: string;
}
```

## Error Handling

### Common Errors

1. **Uncommitted Changes**
   ```typescript
   if (hasUncommittedChanges) {
     throw new Error('Cannot switch branches with uncommitted changes');
   }
   ```

2. **Branch Already Checked Out**
   ```typescript
   // Git automatically prevents this
   // Error: "fatal: 'branch' is already checked out at '/path/to/worktree'"
   ```

3. **Worktree Path Exists**
   ```typescript
   if (await pathExists(worktreePath)) {
     throw new Error(`Path already exists: ${worktreePath}`);
   }
   ```

### Error Recovery

- Show user-friendly warnings
- Fallback to safe defaults
- Never lose user work
- Log detailed errors for debugging

## Performance Considerations

### Caching

- Worktree list cached for 5 seconds
- Branch list cached per repository
- Git status cached with debouncing

### Optimization

```typescript
// Parallel operations where possible
const [branches, worktrees] = await Promise.all([
  loadBranches(repoPath),
  loadWorktrees(repoPath)
]);

// Debounced Git checks
this.gitCheckDebounceTimer = setTimeout(() => {
  this.checkGitRepository();
}, 500);
```

## Security

### Command Injection Prevention

All Git commands use array arguments:
```typescript
// Safe
execFile('git', ['checkout', branchName])

// Never use string concatenation
// execFile('git checkout ' + branchName) // DANGEROUS
```

### Path Validation

```typescript
// Resolve and validate paths
const absolutePath = path.resolve(repoPath);
if (!absolutePath.startsWith(allowedBasePath)) {
  throw new Error('Invalid repository path');
}
```

## Worktree Creation

Currently, worktree creation is handled through terminal commands rather than UI:

```bash
# Create a new worktree for an existing branch
git worktree add ../feature-branch feature-branch

# Create a new worktree with a new branch
git worktree add -b new-feature ../new-feature main
```

### UI Support Status

1. **WorktreeManager** (`web/src/client/components/worktree-manager.ts`)
   - No creation UI, only management of existing worktrees
   - Provides worktree switching, deletion, and follow mode controls
   - Shows worktree status (commits ahead, uncommitted changes)

2. **SessionCreateForm** (`web/src/client/components/session-create-form.ts`)
   - Has worktree creation support through the git-branch-selector component
   - ✅ Creates worktrees and updates UI state properly
   - ✅ Selects newly created worktree after creation
   - ✅ Clears loading states and resets form on completion
   - ✅ Comprehensive branch name validation
   - ✅ Specific error messages for common failures
   - ⚠️ Uses simplistic path generation (repo path + branch slug)
   - ❌ No path customization UI
   - ❌ No option to create from specific base branch in UI

3. **Path Generation** (`web/src/client/components/session-create-form/git-utils.ts:100-103`)
   - Simple approach: `${repoPath}-${branchSlug}`
   - Branch names sanitized to alphanumeric + hyphens/underscores
   - No user customization of worktree location

### Missing Features from Spec

1. **Worktree Path Customization**
   - Current: Auto-generated paths only
   - Spec: Should allow custom path input
   - Impact: Users cannot organize worktrees in custom locations

2. **Base Branch Selection in UI**
   - Current: Uses selected base branch from dropdown
   - Missing: No explicit UI to choose base branch during worktree creation
   - Workaround: Select base branch first, then create worktree

3. **Comprehensive E2E Tests**
   - Unit tests exist: `worktrees.test.ts`, `git-hooks.test.ts`
   - Integration tests exist: `worktree-workflows.test.ts`
   - Missing: Full E2E tests for UI worktree creation flow

## Testing

### Unit Tests

- `worktrees.test.ts` - Route handlers
- `git-hooks.test.ts` - Hook installation
- `session-create-form.test.ts` - UI logic

### Integration Tests

- `worktree-workflows.test.ts` - Full workflows
- `follow-mode.test.ts` - Follow mode scenarios

### E2E Tests

- Create worktree via UI
- Switch branches with warnings
- Follow mode synchronization

## Implementation Summary

### ✅ Fully Implemented

1. **Backend API** - All planned endpoints functional
   - List, create, delete, switch, follow mode operations
   - Git hook integration for automatic branch following
   - Proper error handling and validation

2. **Follow Mode** - Complete implementation
   - Git config storage (`vibetunnel.followBranch`)
   - Automatic branch synchronization via hooks
   - UI controls in WorktreeManager and SessionCreateForm

3. **Basic Worktree Creation** - Functional with recent fixes
   - Create new worktrees from SessionCreateForm
   - Branch name validation
   - UI state management
   - Error handling with specific messages

### ⚠️ Partially Implemented

1. **Path Generation** - Simplified version only
   - Auto-generates paths as `${repoPath}-${branchSlug}`
   - No user customization option
   - Works for basic use cases

2. **Testing** - Good coverage but missing E2E
   - Unit tests for routes and utilities
   - Integration tests for workflows
   - Missing: Full E2E tests with UI interactions

### ❌ Not Implemented

1. **Advanced Worktree Creation UI**
   - Custom path input field
   - Path validation and suggestions
   - Preview of final worktree location

2. **WorktreeManager Creation UI**
   - No worktree creation in management view
   - Must use SessionCreateForm or terminal

3. **Worktree Templates/Presets**
   - No saved worktree configurations
   - No quick-create from templates

---

### Worktree (`docs/worktree.md`)

# Git Worktree Management in VibeTunnel

VibeTunnel provides comprehensive Git worktree support, allowing you to work on multiple branches simultaneously without the overhead of cloning repositories multiple times. This guide covers everything you need to know about using worktrees effectively in VibeTunnel.

## Table of Contents

- [What are Git Worktrees?](#what-are-git-worktrees)
- [VibeTunnel's Worktree Features](#vibetunnels-worktree-features)
- [Creating Sessions with Worktrees](#creating-sessions-with-worktrees)
- [Branch Management](#branch-management)
- [Worktree Operations](#worktree-operations)
- [Follow Mode](#follow-mode)
- [Best Practices](#best-practices)
- [Common Workflows](#common-workflows)
- [Troubleshooting](#troubleshooting)

## What are Git Worktrees?

Git worktrees allow you to have multiple working trees attached to the same repository, each checked out to a different branch. This means you can:

- Work on multiple features simultaneously
- Keep a clean main branch while experimenting
- Quickly switch between tasks without stashing changes
- Run tests on one branch while developing on another

## VibeTunnel's Worktree Features

VibeTunnel enhances Git worktrees with:

1. **Visual Worktree Management**: See all worktrees at a glance in the session list
2. **Smart Branch Switching**: Automatically handle branch conflicts and uncommitted changes
3. **Follow Mode**: Keep multiple worktrees in sync when switching branches
4. **Integrated Session Creation**: Create new sessions directly in worktrees
5. **Worktree-aware Terminal Titles**: See which worktree you're working in

## Creating Sessions with Worktrees

### Using the New Session Dialog

When creating a new session in a Git repository, VibeTunnel provides intelligent branch and worktree selection:

1. **Base Branch Selection**
   - When no worktree is selected: "Switch to Branch" - attempts to switch the main repository to the selected branch
   - When creating a worktree: "Base Branch for Worktree" - uses this as the source branch

2. **Worktree Selection**
   - Choose "No worktree (use main repository)" to work in the main checkout
   - Select an existing worktree to create a session there
   - Click "Create new worktree" to create a new worktree on-the-fly

### Smart Branch Switching

When you select a different branch without choosing a worktree:

```
Selected: feature/new-ui
Current: main
Action: Attempts to switch from main to feature/new-ui
```

If the switch fails (e.g., due to uncommitted changes):
- A warning is displayed
- The session is created on the current branch
- No work is lost

### Creating New Worktrees

To create a new worktree from the session dialog:

1. Select your base branch (e.g., `main` or `develop`)
2. Click "Create new worktree"
3. Enter the new branch name
4. Click "Create"

The worktree will be created at: `{repo-path}-{branch-name}`

Example: `/Users/you/project` → `/Users/you/project-feature-awesome`

## Branch Management

### Branch States in VibeTunnel

VibeTunnel shows rich Git information for each session:

- **Branch Name**: Current branch with worktree indicator
- **Ahead/Behind**: Commits ahead/behind the upstream branch
- **Changes**: Uncommitted changes indicator
- **Worktree Status**: Main worktree vs feature worktrees

### Switching Branches

There are several ways to switch branches:

1. **In Main Repository**: Use the branch selector in the new session dialog
2. **In Worktrees**: Each worktree maintains its own branch
3. **With Follow Mode**: Automatically sync the main repository when switching in a worktree

## Worktree Operations

### Listing Worktrees

View all worktrees for a repository:
- In the session list, worktrees are marked with a special indicator
- The autocomplete dropdown shows worktree paths with their branches
- Use the Git app launcher to see a dedicated worktree view

### Creating Worktrees via API

```bash
# Using VibeTunnel's API
curl -X POST http://localhost:4020/api/worktrees \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "repoPath": "/path/to/repo",
    "branch": "feature/new-feature",
    "path": "/path/to/repo-new-feature",
    "baseBranch": "main"
  }'
```

### Deleting Worktrees

Remove worktrees when no longer needed:

```bash
# Via API
curl -X DELETE "http://localhost:4020/api/worktrees/feature-branch?repoPath=/path/to/repo" \
  -H "Authorization: Bearer YOUR_TOKEN"

# With force option for worktrees with uncommitted changes
curl -X DELETE "http://localhost:4020/api/worktrees/feature-branch?repoPath=/path/to/repo&force=true" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

## Follow Mode

Follow mode keeps your main repository synchronized with a specific worktree. This allows agents to work in worktrees while your IDE, Xcode, and servers stay open on the main repository - they'll automatically update when the worktree changes.

### How It Works

1. Enable follow mode from either the main repo or a worktree
2. Git hooks in both locations detect changes (commits, branch switches, checkouts)
3. Changes in the worktree sync to the main repository
4. Commits in the main repository sync to the worktree
5. Branch switches in the main repository auto-disable follow mode

Follow mode state is stored in the main repository's git config:
```bash
# Check which worktree is being followed
git config vibetunnel.followWorktree

# Returns the path to the followed worktree when active
```

### Using Follow Mode with vt

From a worktree:
```bash
# Enable follow mode for this worktree
vt follow
# Output: Enabling follow mode for worktree: ~/project-feature
#         Main repository (~/project) will track this worktree
```

From main repository:
```bash
# Follow current branch's worktree (if it exists)
vt follow

# Follow a specific branch's worktree
vt follow feature/new-feature

# Follow a worktree by path
vt follow ~/project-feature

# Disable follow mode
vt unfollow
```

The `vt follow` command is smart:
- From worktree: Always follows the current worktree
- From main repo without args: Follows current branch's worktree if it exists
- From main repo with args: Can specify branch name or worktree path

### Checking Follow Mode Status

```bash
# Check current follow mode in git config
git config vibetunnel.followBranch

# If output shows a branch name, follow mode is enabled for that branch
# If no output, follow mode is disabled
```

### Use Cases

- **Agent Development**: Agents work in worktrees while your IDE/Xcode stays on main repo
- **Continuous Development**: Keep servers running without restarts when switching features
- **Testing**: Make changes in worktree, test immediately in main repo environment
- **Parallel Work**: Multiple agents in different worktrees, switch follow mode as needed
- **Zero Disruption**: Never close your IDE or restart servers when context switching

## Best Practices

### 1. Naming Conventions

Use descriptive branch names that work well as directory names:
- ✅ `feature/user-authentication`
- ✅ `bugfix/memory-leak`
- ❌ `fix/issue#123` (special characters)

### 2. Worktree Organization

Keep worktrees organized:
```
~/projects/
  myapp/              # Main repository
  myapp-feature-auth/ # Feature worktree
  myapp-bugfix-api/   # Bugfix worktree
  myapp-release-2.0/  # Release worktree
```

### 3. Cleanup

Regularly clean up unused worktrees:
- Remove merged feature branches
- Prune worktrees for deleted remote branches
- Use `git worktree prune` to clean up references

### 4. Performance

- Limit active worktrees to what you're actively working on
- Use follow mode judiciously (it triggers branch switches)
- Close sessions in unused worktrees to free resources

## Common Workflows

### Quick Start with Follow Mode

```bash
# Create a worktree for agent development
git worktree add ../myproject-feature feature/awesome

# From the worktree, enable follow mode
cd ../myproject-feature
vt follow  # Main repo will now track this worktree

# Or from the main repo
cd ../myproject
vt follow ../myproject-feature  # Same effect
```

### Feature Development

1. Create a worktree for your feature branch
   ```bash
   git worktree add ../project-feature feature/new-ui
   ```
2. Enable follow mode
   ```bash
   # From the worktree
   cd ../project-feature
   vt follow
   
   # Or from main repo
   cd ../project
   vt follow feature/new-ui
   ```
3. Agent develops in worktree while you stay in main repo
4. Your IDE and servers automatically see updates
5. Merge and remove worktree when done

### Agent-Assisted Development

```bash
# Create worktree for agent
git worktree add ../project-agent feature/ai-feature

# Enable follow mode from main repo
vt follow ../project-agent

# Agent works in worktree, your main repo stays in sync
# Switch branches in worktree? Main repo follows
# Commit in worktree? Main repo updates

# When done
vt unfollow
```

### Bug Fixes

1. Create worktree from production branch
   ```bash
   git worktree add ../project-hotfix hotfix/critical-bug
   ```
2. Switch to it with follow mode
   ```bash
   vt follow hotfix/critical-bug
   ```
3. Fix the bug and test
4. Cherry-pick to other branches if needed
5. Clean up worktree after merge

### Parallel Development

1. Keep main repo on stable branch with IDE/servers running
2. Create worktrees for different features
3. Use `vt follow ~/project-feature1` to track first feature
4. Switch to `vt follow ~/project-feature2` for second feature
5. Main repo instantly syncs without restarting anything

## Troubleshooting

### "Cannot switch branches due to uncommitted changes"

**Problem**: Trying to switch branches with uncommitted work
**Solution**: 
- Commit or stash your changes first
- Use a worktree to work on the other branch
- VibeTunnel will show a warning and stay on current branch

### "Worktree path already exists"

**Problem**: Directory already exists when creating worktree
**Solution**:
- Choose a different name for your branch
- Manually remove the existing directory
- Use the `-force` option if appropriate

### "Branch already checked out in another worktree"

**Problem**: Git prevents checking out the same branch in multiple worktrees
**Solution**:
- Use the existing worktree for that branch
- Create a new branch from the desired branch
- Remove the other worktree if no longer needed

### Worktree Not Showing in List

**Problem**: Created worktree doesn't appear in VibeTunnel
**Solution**:
- Ensure the worktree is within a discoverable path
- Check that Git recognizes it: `git worktree list`
- Refresh the repository discovery in VibeTunnel

### Follow Mode Not Working

**Problem**: Main repository doesn't follow worktree changes
**Solution**:
- Ensure you enabled follow mode: `git config vibetunnel.followWorktree`
- Check hooks are installed in both repos: `ls -la .git/hooks/post-*`
- Verify worktree path is correct: `vt status`
- Check for uncommitted changes in main repo blocking sync
- If you switched branches in main repo, follow mode auto-disabled

## Advanced Topics

### Custom Worktree Locations

You can create worktrees in custom locations:

```bash
# Create in a specific directory
git worktree add /custom/path/feature-branch feature/branch

# VibeTunnel will still discover and manage it
```

### Bare Repositories

For maximum flexibility, use a bare repository with worktrees:

```bash
# Clone as bare
git clone --bare https://github.com/user/repo.git repo.git

# Create worktrees from bare repo
git -C repo.git worktree add ../repo-main main
git -C repo.git worktree add ../repo-feature feature/branch
```

### Integration with CI/CD

Use worktrees for CI/CD workflows:
- Keep a clean worktree for builds
- Test multiple branches simultaneously
- Isolate deployment branches

## Command Reference

### vt Commands
- `vt follow` - Enable follow mode for current branch
- `vt follow <branch>` - Switch to branch and enable follow mode
- `vt unfollow` - Disable follow mode
- `vt git event` - Used internally by Git hooks

### Git Commands
- `git worktree add <path> <branch>` - Create a new worktree
- `git worktree list` - List all worktrees
- `git worktree remove <path>` - Remove a worktree

### API Reference

For detailed API documentation, see the main [API specification](./spec.md#worktree-endpoints).

Key endpoints:
- `GET /api/worktrees` - List worktrees with current follow mode status
- `POST /api/worktrees/follow` - Enable/disable follow mode for a branch
- `GET /api/git/follow` - Check follow mode status for a repository
- `POST /api/git/event` - Internal endpoint used by git hooks

## Conclusion

Git worktrees in VibeTunnel provide a powerful way to manage multiple branches and development tasks. By understanding the branch switching behavior, follow mode, and best practices, you can significantly improve your development workflow.

For implementation details and architecture, see the [Worktree Implementation Spec](./worktree-spec.md).

---

### Apple Docs Logging Private Fix (`apple/docs/logging-private-fix.md`)

# Fixing macOS Log Redaction for VibeTunnel

## Quick Fix: Configuration Profile (Recommended)

We provide a ready-to-use configuration profile that enables full debug logging for VibeTunnel:

**Location**: `apple/logging/VibeTunnel-Logging.mobileconfig`

### Installing the Profile

#### macOS
1. Double-click `apple/logging/VibeTunnel-Logging.mobileconfig`
2. System Settings will open to the Profiles section
3. Click "Install..." and enter your password
4. Restart VibeTunnel

#### iOS
1. AirDrop or email the profile to your device
2. Open Settings → General → VPN & Device Management
3. Install the "VibeTunnel Debug Logging" profile
4. Restart the VibeTunnel app

### Verifying It Works
```bash
# You should now see full details instead of <private>
./scripts/vtlog.sh
```

### Removing the Profile
- **macOS**: System Settings → Privacy & Security → Profiles → Remove
- **iOS**: Settings → General → VPN & Device Management → Remove Profile

## The Problem

When viewing VibeTunnel logs using Apple's unified logging system, you'll see `<private>` instead of actual values:

```
2025-07-05 08:40:08.062262+0100 VibeTunnel: Failed to connect to <private> after <private> seconds
```

This makes debugging extremely difficult as you can't see session IDs, URLs, or other important debugging information.

## Why Apple Does This

Apple redacts dynamic values in logs by default to protect user privacy:
- Prevents accidental logging of passwords, tokens, or personal information
- Logs can be accessed by other apps with proper entitlements
- Helps apps comply with privacy regulations (GDPR, etc.)

## The Solution: Passwordless sudo for log command

### Step 1: Edit sudoers file

```bash
sudo visudo
```

### Step 2: Add the NOPASSWD rule

Add this line at the end of the file (replace `yourusername` with your actual username):

```
yourusername ALL=(ALL) NOPASSWD: /usr/bin/log
```

For example, if your username is `steipete`:
```
steipete ALL=(ALL) NOPASSWD: /usr/bin/log
```

### Step 3: Save and exit

- Press `Esc` to enter command mode
- Type `:wq` and press Enter to save and quit
- The changes take effect immediately

### Step 4: Test it

```bash
# This should work without asking for password:
sudo -n log show --last 1s

# Now vtlog.sh with private flag works without password:
./scripts/vtlog.sh -p
```

## How It Works

1. **Normal log viewing** (redacted):
   ```bash
   log show --predicate 'subsystem == "sh.vibetunnel.vibetunnel"'
   # Shows: Connected to <private>
   ```

2. **With sudo and --info flag** (reveals private data):
   ```bash
   sudo log show --predicate 'subsystem == "sh.vibetunnel.vibetunnel"' --info
   # Shows: Connected to session-123abc
   ```

3. **vtlog.sh -p flag** automatically:
   - Adds `sudo` to the command
   - Adds `--info` flag to reveal private data
   - With our sudoers rule, no password needed\!

## Security Considerations

### What this allows:
- ✅ Passwordless access to `log` command only
- ✅ Can view all system logs without password
- ✅ Can stream logs in real-time

### What this does NOT allow:
- ❌ Cannot run other commands with sudo
- ❌ Cannot modify system files
- ❌ Cannot install software
- ❌ Cannot change system settings

### Best Practices:
1. Only grant this permission to trusted developer accounts
2. Use the most restrictive rule possible
3. Consider removing when not actively debugging
4. Never use `NOPASSWD: ALL` - always specify exact commands

## Alternative Solutions

### 1. Touch ID for sudo (if you have a Mac with Touch ID)

Edit `/etc/pam.d/sudo`:
```bash
sudo vi /etc/pam.d/sudo
```

Add this line at the top (after the comment):
```
auth       sufficient     pam_tid.so
```

Now you can use your fingerprint instead of typing password.

### 2. Extend sudo timeout

Make sudo remember your password longer:
```bash
sudo visudo
```

Add:
```
Defaults timestamp_timeout=60
```

This keeps sudo active for 60 minutes after each use.

### 3. Fix in Swift code

Mark non-sensitive values as public in your Swift logging:
```swift
// Before (will show as <private>):
logger.info("Connected to \(sessionId)")

// After (always visible):
logger.info("Connected to \(sessionId, privacy: .public)")
```

### 4. Configure logging system (Modern Methods)

#### Method A: Plist File (Recommended)

Create a plist file to enable private data logging for VibeTunnel:

```bash
# Create the directory if it doesn't exist
sudo mkdir -p /Library/Preferences/Logging/Subsystems

# Create the plist file
sudo tee /Library/Preferences/Logging/Subsystems/sh.vibetunnel.vibetunnel.plist > /dev/null << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Enable-Private-Data</key>
    <true/>
</dict>
</plist>
EOF

# Verify it was created
ls -la /Library/Preferences/Logging/Subsystems/sh.vibetunnel.vibetunnel.plist
```

To remove:
```bash
sudo rm /Library/Preferences/Logging/Subsystems/sh.vibetunnel.vibetunnel.plist
```

#### Method B: Configuration Profile

For managed environments or multiple subsystems, create a configuration profile:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>PayloadContent</key>
    <array>
        <dict>
            <key>PayloadType</key>
            <string>com.apple.system.logging</string>
            <key>PayloadIdentifier</key>
            <string>com.example.logging.vibetunnel</string>
            <key>PayloadUUID</key>
            <string>$(uuidgen)</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>Subsystems</key>
            <dict>
                <key>sh.vibetunnel.vibetunnel</key>
                <dict>
                    <key>Enable-Private-Data</key>
                    <true/>
                </dict>
            </dict>
        </dict>
    </array>
    <key>PayloadIdentifier</key>
    <string>com.example.vibetunnel.logging</string>
    <key>PayloadUUID</key>
    <string>$(uuidgen)</string>
    <key>PayloadType</key>
    <string>Configuration</string>
    <key>PayloadVersion</key>
    <integer>1</integer>
</dict>
</plist>
```

Install via System Settings → Profiles.

**Note:** The old `private_data:on` flag was removed in macOS Catalina and no longer works.

## Using vtlog.sh

With passwordless sudo configured, you can now use:

```bash
# View all logs with private data visible
./scripts/vtlog.sh -p

# Filter by category with private data
./scripts/vtlog.sh -p -c WebRTCManager

# Follow logs in real-time with private data
./scripts/vtlog.sh -p -f

# Search for errors with private data visible
./scripts/vtlog.sh -p -s "error" -n 1h

# Combine filters
./scripts/vtlog.sh -p -c ServerManager -s "connection" -f
```

## Troubleshooting

### "sudo: a password is required"
- Make sure you saved the sudoers file (`:wq` in vi)
- Try in a new terminal window
- Run `sudo -k` to clear sudo cache, then try again
- Verify the line exists: `sudo grep NOPASSWD /etc/sudoers`

### "syntax error" when saving sudoers
- Never edit `/etc/sudoers` directly\!
- Always use `sudo visudo` - it checks syntax before saving
- Make sure the line format is exactly:
  ```
  username ALL=(ALL) NOPASSWD: /usr/bin/log
  ```

### Changes not taking effect
- Close and reopen your terminal
- Make sure you're using the exact username from `whoami`
- Check that `/usr/bin/log` exists: `ls -la /usr/bin/log`

### Still seeing <private> with -p flag
- Verify sudo works: `sudo -n log show --last 1s`
- Check vtlog.sh has execute permissions: `chmod +x scripts/vtlog.sh`
- Make sure you're using `-p` flag: `./scripts/vtlog.sh -p`

## Summary

The passwordless sudo configuration for `/usr/bin/log` is the cleanest solution:
- Works immediately after setup
- No password prompts when debugging
- Limited security risk (only affects log viewing)
- Easy to revert if needed

Combined with `vtlog.sh -p`, you get a smooth debugging experience without the frustration of `<private>` tags hiding important information.
ENDOFFILE < /dev/null

---

### Apple Docs Modern Swift (`apple/docs/modern-swift.md`)

# Modern Swift Development

Write idiomatic SwiftUI code following Apple's latest architectural recommendations and best practices.

## Core Philosophy

- SwiftUI is the default UI paradigm for Apple platforms - embrace its declarative nature
- Avoid legacy UIKit patterns and unnecessary abstractions
- Focus on simplicity, clarity, and native data flow
- Let SwiftUI handle the complexity - don't fight the framework

## Architecture Guidelines

### 1. Embrace Native State Management

Use SwiftUI's built-in property wrappers appropriately:
- `@State` - Local, ephemeral view state
- `@Binding` - Two-way data flow between views
- `@Observable` - Shared state (iOS 17+)
- `@ObservableObject` - Legacy shared state (pre-iOS 17)
- `@Environment` - Dependency injection for app-wide concerns

### 2. State Ownership Principles

- Views own their local state unless sharing is required
- State flows down, actions flow up
- Keep state as close to where it's used as possible
- Extract shared state only when multiple views need it

### 3. Modern Async Patterns

- Use `async/await` as the default for asynchronous operations
- Leverage `.task` modifier for lifecycle-aware async work
- Avoid Combine unless absolutely necessary
- Handle errors gracefully with try/catch

### 4. View Composition

- Build UI with small, focused views
- Extract reusable components naturally
- Use view modifiers to encapsulate common styling
- Prefer composition over inheritance

### 5. Code Organization

- Organize by feature, not by type (avoid Views/, Models/, ViewModels/ folders)
- Keep related code together in the same file when appropriate
- Use extensions to organize large files
- Follow Swift naming conventions consistently

## Implementation Patterns

### Simple State Example
```swift
struct CounterView: View {
    @State private var count = 0
    
    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") { 
                count += 1 
            }
        }
    }
}
```

### Shared State with @Observable
```swift
@Observable
class UserSession {
    var isAuthenticated = false
    var currentUser: User?
    
    func signIn(user: User) {
        currentUser = user
        isAuthenticated = true
    }
}

struct MyApp: App {
    @State private var session = UserSession()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(session)
        }
    }
}
```

### Async Data Loading
```swift
struct ProfileView: View {
    @State private var profile: Profile?
    @State private var isLoading = false
    @State private var error: Error?
    
    var body: some View {
        Group {
            if isLoading {
                ProgressView()
            } else if let profile {
                ProfileContent(profile: profile)
            } else if let error {
                ErrorView(error: error)
            }
        }
        .task {
            await loadProfile()
        }
    }
    
    private func loadProfile() async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            profile = try await ProfileService.fetch()
        } catch {
            self.error = error
        }
    }
}
```

## Best Practices

### DO:
- Write self-contained views when possible
- Use property wrappers as intended by Apple
- Test logic in isolation, preview UI visually
- Handle loading and error states explicitly
- Keep views focused on presentation
- Use Swift's type system for safety

### DON'T:
- Create ViewModels for every view
- Move state out of views unnecessarily
- Add abstraction layers without clear benefit
- Use Combine for simple async operations
- Fight SwiftUI's update mechanism
- Overcomplicate simple features

## Testing Strategy

- Unit test business logic and data transformations
- Use SwiftUI Previews for visual testing
- Test @Observable classes independently
- Keep tests simple and focused
- Don't sacrifice code clarity for testability

## Modern Swift Features

- Use Swift Concurrency (async/await, actors)
- Leverage Swift 6 data race safety when available
- Utilize property wrappers effectively
- Embrace value types where appropriate
- Use protocols for abstraction, not just for testing

## Summary

Write SwiftUI code that looks and feels like SwiftUI. The framework has matured significantly - trust its patterns and tools. Focus on solving user problems rather than implementing architectural patterns from other platforms.

---

### Apple Docs Swift Concurrency (`apple/docs/swift-concurrency.md`)

# Swift 6 Concurrency: Complete Guide to Data Race Safety and Strict Concurrency

## Table of Contents

1. [Introduction](#introduction)
2. [Core Concepts](#core-concepts)
3. [Swift Evolution Proposals](#swift-evolution-proposals)
4. [Data Race Safety](#data-race-safety)
5. [Actor System](#actor-system)
6. [Sendable Protocol](#sendable-protocol)
7. [Migration Guide](#migration-guide)
8. [Code Examples](#code-examples)
9. [Best Practices](#best-practices)
10. [Testing and Debugging](#testing-and-debugging)
11. [Common Compiler Errors](#common-compiler-errors)
12. [Performance Tuning](#performance-tuning)
13. [Swift 6.1+ Roadmap](#swift-61-roadmap)
14. [Resources](#resources)

## Introduction

Swift 6 represents a revolutionary leap in concurrent programming, introducing compile-time data race safety that eliminates an entire class of bugs. This comprehensive guide covers all Swift Evolution proposals, practical examples, and migration strategies for adopting Swift 6's strict concurrency model.

### Quick Start: Enable Strict Concurrency

**Xcode:**
```
Build Settings → Swift Compiler - Language → Strict Concurrency Checking → Complete
```

**Command Line:**
```bash
swiftc -strict-concurrency=complete -swift-version 6 MyFile.swift
```

**Package.swift:**
```swift
.target(
    name: "MyTarget",
    swiftSettings: [
        .enableUpcomingFeature("StrictConcurrency"),
        .swiftLanguageMode(.v6)  // or stay on .v5 with warnings
    ]
)
```

### What is Swift 6 Concurrency?

Swift 6's concurrency model builds upon Swift 5.5's async/await foundation with:
- **Complete data race safety** at compile time
- **Actor isolation** enforcement
- **Sendable protocol** requirements
- **Region-based isolation** for smarter type checking
- **Improved ergonomics** reducing false positives

### Key Benefits

1. **Compile-time Safety**: Catch data races before runtime
2. **Progressive Migration**: Adopt incrementally with warnings first
3. **Better Performance**: Compiler optimizations enabled by isolation guarantees
4. **Clearer Intent**: Explicit concurrency boundaries in code

## Core Concepts

### 1. Isolation Domains

Swift 6 defines clear isolation domains where data can be safely accessed:

```swift
// MainActor isolation domain - UI code
@MainActor
class ViewController: UIViewController {
    var label = UILabel() // Safe within MainActor
    
    func updateUI() {
        label.text = "Updated" // No await needed - same isolation
    }
}

// Custom actor isolation domain
actor DataManager {
    private var cache: [String: Data] = [:] // Protected by actor
    
    func store(key: String, data: Data) {
        cache[key] = data // Safe within actor
    }
}

// No isolation - must be Sendable
struct Point: Sendable {
    let x: Double
    let y: Double
}
```

### 2. Concurrency Boundaries

Data crossing concurrency boundaries must be Sendable:

```swift
// ❌ Swift 5: Potential data race
class Model {
    var items: [Item] = []
}

func process(model: Model) async {
    await Task.detached {
        model.items.append(Item()) // Data race!
    }.value
}

// ✅ Swift 6: Compile-time error prevents data race
@MainActor
final class Model: Sendable {
    private(set) var items: [Item] = []
    
    func addItem(_ item: Item) {
        items.append(item) // Safe - MainActor synchronized
    }
}
```

### 3. Region-Based Isolation

Swift 6 introduces "isolation regions" that track data flow:

```swift
// Region-based isolation allows safe transfer without Sendable
func processImage(_ image: UIImage) async -> ProcessedImage {
    // Swift 6 proves image won't be accessed after transfer
    let processed = await withTaskGroup(of: ProcessedTile.self) { group in
        for tile in image.tiles {
            group.addTask {
                // Safe transfer - compiler tracks regions
                await processTile(tile)
            }
        }
        // Collect results...
    }
    return processed
}
```

## Swift Evolution Proposals

### Foundation Proposals (Swift 5.5-5.10)

#### SE-0302: Sendable and @Sendable
Introduces the fundamental `Sendable` protocol:

```swift
// Types safe to share across concurrency domains
protocol Sendable {}

// Sendable closure
let operation: @Sendable () -> Void = {
    print("This closure captures only Sendable values")
}

// Conditional Sendable
struct Container<T>: Sendable where T: Sendable {
    let value: T
}
```

#### SE-0306: Actors
The actor model for protecting mutable state:

```swift
actor BankAccount {
    private var balance: Decimal = 0
    
    func deposit(amount: Decimal) {
        balance += amount
    }
    
    func withdraw(amount: Decimal) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }
    
    // Computed property accessible without await
    nonisolated var accountDescription: String {
        "Bank Account" // No state access
    }
}
```

#### SE-0316: Global Actors
System-wide isolation domains:

```swift
@globalActor
actor DataActor {
    static let shared = DataActor()
}

// Apply to entire type
@DataActor
class DataStore {
    var items: [Item] = []
    
    func add(_ item: Item) {
        items.append(item)
    }
}

// Apply to specific members
class MixedClass {
    @DataActor var data: [String] = []
    @MainActor var uiState = UIState()
    
    @DataActor
    func processData() async {
        // Runs on DataActor
    }
    
    @MainActor
    func updateUI() {
        // Runs on MainActor
    }
}
```

### Swift 6 Core Proposals

#### SE-0337: Incremental Migration to Concurrency Checking
Enables progressive adoption:

```swift
// Package.swift
.target(
    name: "MyTarget",
    swiftSettings: [
        .enableUpcomingFeature("StrictConcurrency"),
        .enableUpcomingFeature("CompleteAsync"),
        .enableExperimentalFeature("StrictConcurrency=minimal")
    ]
)

// Or via compiler flag
// swiftc -strict-concurrency=complete
```

#### SE-0401: Remove Actor Isolation Inference from Property Wrappers
Eliminates unexpected isolation:

```swift
// Before SE-0401
struct ContentView: View {
    @StateObject private var model = Model() // Made View MainActor-isolated!
    
    func doWork() { // Implicitly @MainActor
        // ...
    }
}

// After SE-0401
struct ContentView: View {
    @StateObject private var model = Model() // No isolation inference
    
    nonisolated func doWork() { // Explicitly non-isolated
        // ...
    }
}
```

#### SE-0412: Strict Concurrency for Global Variables
Global variable safety with `nonisolated(unsafe)`:

```swift
// ❌ Swift 6 error: global variable not concurrency-safe
var sharedCache: [String: Data] = [:]

// ✅ Option 1: Make it a let constant
let sharedConstants = Constants()

// ✅ Option 2: Use global actor
@MainActor
var sharedUICache: [String: UIImage] = [:]

// ✅ Option 3: Actor isolation
actor CacheActor {
    static let shared = CacheActor()
    private var cache: [String: Data] = [:]
}

// ✅ Option 4: Explicit unsafe opt-out
struct LegacyAPI {
    nonisolated(unsafe) static var shared: LegacyAPI?
}
```

#### SE-0414: Region-Based Isolation
Revolutionary improvement in isolation checking:

```swift
// Non-Sendable type can be safely transferred
class MutableData {
    var value: Int = 0
}

func process() async {
    let data = MutableData() // Non-Sendable
    
    // ✅ Safe: data not used after transfer
    await withTaskGroup(of: Void.self) { group in
        group.addTask {
            data.value = 42 // Region analysis proves safety
        }
    }
    
    // ❌ Error: data used after transfer
    // print(data.value)
}
```

#### SE-0420: Inheritance of Actor Isolation
Dynamic isolation inheritance:

```swift
// Function inherits caller's isolation
func log(
    _ message: String,
    isolation: isolated (any Actor)? = #isolation
) async {
    print("[\(isolation)] \(message)")
}

@MainActor
func updateUI() async {
    await log("Updating UI") // Inherits MainActor isolation
}

actor DataProcessor {
    func process() async {
        await log("Processing") // Inherits DataProcessor isolation
    }
}
```

#### SE-0430: Sending Parameter and Result Values
Safe transfer without full Sendable:

```swift
// 'sending' allows ownership transfer
func processData(_ data: sending MutableData) async -> sending ProcessedData {
    // data is consumed - original reference invalidated
    return ProcessedData(from: data)
}

// Updated Task API
extension Task where Failure == Never {
    init(
        priority: TaskPriority? = nil,
        operation: sending @escaping () async -> Success
    )
}
```

#### SE-0431: @isolated(any) Function Types
Isolation-agnostic function types:

```swift
// Function type that preserves any isolation
typealias IsolatedOperation = @isolated(any) () async -> Void

struct Executor {
    func run(_ operation: IsolatedOperation) async {
        await operation() // Maintains caller's isolation
    }
}
```

#### SE-0434: Usability of Global-Actor-Isolated Types
Improvements for global actor usage:

```swift
// Sendable properties can be nonisolated
@MainActor
final class ViewModel: Sendable {
    // ✅ Implicitly nonisolated (Sendable stored property)
    let id = UUID()
    
    // ❌ Must be isolated (non-Sendable)
    var items: [Item] = []
    
    // ✅ Can be explicitly nonisolated
    nonisolated let configuration: Configuration
}

// Improved inference for closures
@MainActor
class Controller {
    func setup() {
        // ✅ Closure inferred as @MainActor @Sendable
        Task {
            await updateData()
        }
    }
}
```

## Data Race Safety

### Complete Concurrency Checking

Swift 6 enforces complete checking by default:

```swift
// Enable in Swift 5 mode for migration
// swift -strict-concurrency=complete

// Levels of checking:
// 1. minimal - Only explicit Sendable conformances
// 2. targeted - Infer Sendable for some types
// 3. complete - Full data race checking
```

### Common Data Race Patterns and Fixes

#### Pattern 1: Shared Mutable State

```swift
// ❌ Data race
class Counter {
    var value = 0
    
    func increment() {
        value += 1 // Race condition!
    }
}

// ✅ Fix 1: Use an actor
actor Counter {
    private var value = 0
    
    func increment() {
        value += 1 // Actor-isolated
    }
    
    var currentValue: Int {
        value
    }
}

// ✅ Fix 2: Use atomic operations
import Atomics

final class Counter: Sendable {
    private let value = ManagedAtomic<Int>(0)
    
    func increment() {
        value.wrappingIncrement(ordering: .relaxed)
    }
    
    var currentValue: Int {
        value.load(ordering: .relaxed)
    }
}
```

#### Pattern 2: Callback Isolation

```swift
// ❌ Unclear isolation
class NetworkManager {
    func fetch(completion: @escaping (Data) -> Void) {
        URLSession.shared.dataTask(with: url) { data, _, _ in
            completion(data!) // What thread?
        }
    }
}

// ✅ Clear isolation with async/await
class NetworkManager {
    func fetch() async throws -> Data {
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
}

// ✅ Or explicit MainActor isolation
class NetworkManager {
    func fetch(completion: @MainActor @escaping (Data) -> Void) {
        Task {
            let data = try await URLSession.shared.data(from: url).0
            await completion(data)
        }
    }
}
```

## Actor System

### Basic Actor Usage

```swift
actor DatabaseConnection {
    private var isConnected = false
    private var activeQueries = 0
    
    func connect() async throws {
        guard !isConnected else { return }
        // Connection logic...
        isConnected = true
    }
    
    func query(_ sql: String) async throws -> [Row] {
        activeQueries += 1
        defer { activeQueries -= 1 }
        
        // Query execution...
        return rows
    }
    
    // Synchronous access for immutable data
    nonisolated let connectionString: String
    
    // Computed property without state access
    nonisolated var description: String {
        "Database connection to \(connectionString)"
    }
}
```

### MainActor for UI Code

```swift
// Entire class on MainActor
@MainActor
final class LoginViewModel: ObservableObject {
    @Published private(set) var isLoading = false
    @Published private(set) var error: Error?
    
    func login(username: String, password: String) async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            // This switches to background for network call
            let user = try await AuthService.shared.login(
                username: username,
                password: password
            )
            // Automatically back on MainActor
            navigateToHome(user: user)
        } catch {
            self.error = error
        }
    }
    
    // Can run on any thread
    nonisolated func validateEmail(_ email: String) -> Bool {
        // Email validation logic...
    }
}
```

### Custom Global Actors

```swift
// Define a global actor for database operations
@globalActor
actor DatabaseActor {
    static let shared = DatabaseActor()
    
    // Custom executor for integration
    nonisolated var unownedExecutor: UnownedSerialExecutor {
        DatabaseQueue.shared.unownedExecutor
    }
}

// Apply to types handling database operations
@DatabaseActor
class UserRepository {
    private var cache: [UUID: User] = [:]
    
    func findUser(id: UUID) async throws -> User {
        if let cached = cache[id] {
            return cached
        }
        
        let user = try await database.fetch(User.self, id: id)
        cache[id] = user
        return user
    }
    
    func saveUser(_ user: User) async throws {
        try await database.save(user)
        cache[user.id] = user
    }
}

// Mix different actors in one type
class DataCoordinator {
    @DatabaseActor
    private var userRepo = UserRepository()
    
    @MainActor
    private var viewModel = UserListViewModel()
    
    func refreshUsers() async {
        // Fetch on DatabaseActor
        let users = await userRepo.fetchAllUsers()
        
        // Update on MainActor
        await viewModel.update(users: users)
    }
}
```

## Sendable Protocol

### Understanding Sendable

```swift
// Sendable indicates thread-safe types
public protocol Sendable {}

// Automatic conformance for:
// 1. Actors (handle synchronization)
// 2. Immutable structs/enums
// 3. Final classes with immutable storage
// 4. @unchecked Sendable for manual safety

// Examples of automatic Sendable
struct Point: Sendable { // Implicit
    let x: Double
    let y: Double
}

enum Status: Sendable { // Implicit
    case pending
    case completed(at: Date)
}

actor DataManager {} // Implicitly Sendable

final class User: Sendable {
    let id: UUID
    let name: String
    // All stored properties are immutable
}
```

### Conditional Sendable

```swift
// Generic types can be conditionally Sendable
struct Container<T> {
    let value: T
}

// Automatic conditional conformance
extension Container: Sendable where T: Sendable {}

// Custom conditional conformance
struct Cache<Key: Hashable, Value> {
    private var storage: [Key: Value] = [:]
    private let lock = NSLock()
}

extension Cache: @unchecked Sendable where Key: Sendable, Value: Sendable {
    // We ensure thread safety with lock
}
```

### @unchecked Sendable

```swift
// For types that are thread-safe but can't be proven by compiler
final class ThreadSafeCache: @unchecked Sendable {
    private var cache: [String: Data] = [:]
    private let queue = DispatchQueue(label: "cache.queue")
    
    func get(_ key: String) -> Data? {
        queue.sync { cache[key] }
    }
    
    func set(_ key: String, value: Data) {
        queue.async { self.cache[key] = value }
    }
}

// Reference types with immutable data
final class ImageWrapper: @unchecked Sendable {
    let cgImage: CGImage
    
    init(cgImage: CGImage) {
        self.cgImage = cgImage
    }
}
```

### Sendable Functions and Closures

```swift
// Sendable function types
typealias AsyncOperation = @Sendable () async -> Void
typealias CompletionHandler = @Sendable (Result<Data, Error>) -> Void

// Using Sendable closures
func performAsync(operation: @Sendable @escaping () async -> Void) {
    Task {
        await operation()
    }
}

// Sendable captures
func createTimer(interval: TimeInterval) -> AsyncStream<Date> {
    AsyncStream { continuation in
        let timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in
            continuation.yield(Date())
        }
        
        continuation.onTermination = { @Sendable _ in
            timer.invalidate() // Must be Sendable
        }
    }
}
```

## Migration Guide

### Step 1: Enable Warnings in Swift 5 Mode

```swift
// In Package.swift
.target(
    name: "MyApp",
    swiftSettings: [
        .enableUpcomingFeature("StrictConcurrency"),
        .enableUpcomingFeature("ExistentialAny"),
        .enableUpcomingFeature("ConciseMagicFile")
    ]
)

// Or in Xcode Build Settings
// Strict Concurrency Checking: Complete
// SWIFT_STRICT_CONCURRENCY = complete
```

### Step 2: Fix Global Variables

```swift
// Before
var sharedFormatter = DateFormatter()

// After - Option 1: Make immutable
let sharedFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    return formatter
}()

// After - Option 2: Add actor isolation
extension DateFormatter {
    @MainActor
    static let shared: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        return formatter
    }()
}

// After - Option 3: Use nonisolated(unsafe) for legacy code
nonisolated(unsafe) var legacyGlobal: LegacyType?
```

### Step 3: Add Sendable Conformances

```swift
// Make your model types Sendable
struct User: Codable, Sendable {
    let id: UUID
    let name: String
    let email: String
}

// For reference types, ensure immutability
final class Configuration: Sendable {
    let apiKey: String
    let baseURL: URL
    
    init(apiKey: String, baseURL: URL) {
        self.apiKey = apiKey
        self.baseURL = baseURL
    }
}
```

### Step 4: Isolate UI Code

```swift
// Before
class ViewModel: ObservableObject {
    @Published var items: [Item] = []
    
    func loadItems() {
        Task {
            items = await fetchItems() // Potential race
        }
    }
}

// After
@MainActor
final class ViewModel: ObservableObject {
    @Published private(set) var items: [Item] = []
    
    func loadItems() async {
        items = await fetchItems() // Safe on MainActor
    }
}
```

### Step 5: Handle Callbacks and Delegates

```swift
// Before - Unclear isolation
protocol DataDelegate: AnyObject {
    func dataDidUpdate(_ data: Data)
}

// After - Explicit isolation
@MainActor
protocol DataDelegate: AnyObject {
    func dataDidUpdate(_ data: Data)
}

// Or use async alternatives
protocol DataProvider {
    func fetchData() async throws -> Data
}
```

### Progressive Migration Strategy

1. **Start with leaf modules**: Begin with modules that have few dependencies
2. **Fix simple issues first**: Immutable globals, missing Sendable conformances
3. **Isolate UI layer**: Add @MainActor to view controllers and view models
4. **Address shared state**: Convert to actors or use synchronization
5. **Enable Swift 6 mode**: Once warnings are resolved

## Code Examples

### Example 1: Image Processing Pipeline

```swift
// Image processor using actors and Sendable
actor ImageProcessor {
    private let cache = ImageCache()
    
    func process(_ image: UIImage, filters: [Filter]) async throws -> UIImage {
        // Check cache
        let cacheKey = CacheKey(image: image, filters: filters)
        if let cached = await cache.get(cacheKey) {
            return cached
        }
        
        // Process image
        var result = image
        for filter in filters {
            result = try await filter.apply(to: result)
        }
        
        // Cache result
        await cache.set(cacheKey, image: result)
        return result
    }
}

// Sendable filter protocol
protocol Filter: Sendable {
    func apply(to image: UIImage) async throws -> UIImage
}

// Concrete filter implementation
struct BlurFilter: Filter {
    let radius: Double
    
    func apply(to image: UIImage) async throws -> UIImage {
        // Implementation using Core Image
        let ciImage = CIImage(image: image)!
        let filter = CIFilter.gaussianBlur()
        filter.inputImage = ciImage
        filter.radius = Float(radius)
        
        let context = CIContext()
        let output = filter.outputImage!
        let cgImage = context.createCGImage(output, from: output.extent)!
        
        return UIImage(cgImage: cgImage)
    }
}
```

### Example 2: Network Layer with Proper Isolation

```
/* Detailed source-code truncated for AI context efficiency. */
```

### Example 3: Concurrent Data Processing

```swift
// Parallel processing with proper isolation
struct DataProcessor {
    func processFiles(_ urls: [URL]) async throws -> [ProcessedData] {
        try await withThrowingTaskGroup(of: ProcessedData.self) { group in
            // Add tasks for each file
            for url in urls {
                group.addTask {
                    try await processFile(url)
                }
            }
            
            // Collect results
            var results: [ProcessedData] = []
            for try await result in group {
                results.append(result)
            }
            
            return results
        }
    }
    
    private func processFile(_ url: URL) async throws -> ProcessedData {
        let data = try await readFile(url)
        let processed = try await transform(data)
        return ProcessedData(
            originalURL: url,
            processedData: processed,
            timestamp: Date()
        )
    }
}

// Result type that's Sendable
struct ProcessedData: Sendable {
    let originalURL: URL
    let processedData: Data
    let timestamp: Date
}
```

## Best Practices

### 1. Design for Sendability

```swift
// ❌ Avoid mutable reference types
class Settings {
    var theme: Theme
    var notifications: Bool
}

// ✅ Prefer value types or immutable reference types
struct Settings: Sendable {
    let theme: Theme
    let notifications: Bool
}

// ✅ Or use actors for mutable state
actor SettingsManager {
    private var settings: Settings
    
    func update(theme: Theme) {
        settings = Settings(
            theme: theme,
            notifications: settings.notifications
        )
    }
}
```

### 2. Minimize Actor Hops

```swift
// ❌ Excessive actor hopping
@MainActor
class ViewModel {
    func processData() async {
        let data = await dataActor.getData()
        let processed = await processorActor.process(data)
        let formatted = await formatterActor.format(processed)
        updateUI(formatted)
    }
}

// ✅ Batch operations
@MainActor
class ViewModel {
    func processData() async {
        let result = await dataActor.getProcessedAndFormattedData()
        updateUI(result)
    }
}
```

### 3. Use nonisolated for Pure Functions

```swift
actor Calculator {
    private var history: [Calculation] = []
    
    // ✅ Pure functions don't need isolation
    nonisolated func add(_ a: Double, _ b: Double) -> Double {
        a + b
    }
    
    nonisolated func multiply(_ a: Double, _ b: Double) -> Double {
        a * b
    }
    
    // State-modifying functions need isolation
    func recordCalculation(_ calc: Calculation) {
        history.append(calc)
    }
}
```

### 4. Leverage Structured Concurrency

```swift
// ✅ Use task groups for parallel work
func downloadImages(urls: [URL]) async throws -> [UIImage] {
    try await withThrowingTaskGroup(of: (Int, UIImage).self) { group in
        for (index, url) in urls.enumerated() {
            group.addTask {
                let image = try await downloadImage(from: url)
                return (index, image)
            }
        }
        
        var images = Array<UIImage?>(repeating: nil, count: urls.count)
        for try await (index, image) in group {
            images[index] = image
        }
        
        return images.compactMap { $0 }
    }
}
```

### 5. Handle Cancellation Properly

```swift
func longRunningOperation() async throws -> Result {
    try await withTaskCancellationHandler {
        var progress = 0.0
        
        while progress < 1.0 {
            try Task.checkCancellation()
            
            // Do work...
            progress += 0.1
            
            try await Task.sleep(for: .seconds(1))
        }
        
        return result
    } onCancel: {
        // Cleanup resources
        cleanupOperation()
    }
}
```

## Testing and Debugging

### Thread Sanitizer (TSan)

Thread Sanitizer remains essential for catching runtime data races that escape compile-time checks:

```bash
# Enable in Xcode
Product → Scheme → Edit Scheme → Diagnostics → Thread Sanitizer

# Or via command line
swift test -Xswiftc -sanitize=thread
```

**When to use TSan:**
- Testing legacy code with @preconcurrency imports
- Verifying @unchecked Sendable implementations
- Catching races in C/Objective-C interop

### Swift Concurrency Debugger (Xcode 16+)

New debugging tools for concurrent code:

1. **Task Tree View**: Visualize parent-child task relationships
2. **Actor Memory Graph**: See actor isolation boundaries
3. **Hop Tracking**: Follow execution across isolation domains

```swift
// Debugging helpers
extension Task {
    static func currentPriority() -> TaskPriority {
        Task.currentPriority
    }
    
    static func printTaskTree() {
        // Available in debug builds
        #if DEBUG
        print("Task: \(Task<Never, Never>.currentPriority)")
        #endif
    }
}
```

### Unit Testing Concurrent Code

```swift
// Test helper for async code with timeout
func withTimeout<T>(
    _ duration: Duration = .seconds(5),
    operation: @escaping () async throws -> T
) async throws -> T {
    try await withThrowingTaskGroup(of: T.self) { group in
        group.addTask {
            try await operation()
        }
        
        group.addTask {
            try await Task.sleep(for: duration)
            throw TimeoutError()
        }
        
        let result = try await group.next()!
        group.cancelAll()
        return result
    }
}

// Testing actor isolation
final class ActorTests: XCTestCase {
    func testActorIsolation() async throws {
        let actor = TestActor()
        
        // Verify isolation with multiple concurrent operations
        try await withThrowingTaskGroup(of: Int.self) { group in
            for i in 0..<100 {
                group.addTask {
                    await actor.increment()
                    return await actor.value
                }
            }
            
            var results: Set<Int> = []
            for try await result in group {
                results.insert(result)
            }
            
            // All results should be unique if properly isolated
            XCTAssertEqual(results.count, 100)
        }
    }
}
```

### Debugging Common Issues

```swift
// 1. Debugging unexpected suspension points
actor DataManager {
    func debugSuspension() async {
        print("Before suspension: \(Thread.current)")
        await someAsyncOperation()
        print("After suspension: \(Thread.current)") // May be different thread
    }
}

// 2. Tracking isolation context
func debugIsolation(
    isolation: isolated (any Actor)? = #isolation
) async {
    if let isolation {
        print("Running on: \(type(of: isolation))")
    } else {
        print("Running on non-isolated context")
    }
}

// 3. Detecting priority inversions
Task(priority: .low) {
    await debugPriority() // May run at higher priority due to escalation
}

func debugPriority() async {
    print("Current priority: \(Task.currentPriority)")
}
```

## Common Compiler Errors

### Error Reference Table

| Diagnostic | Example | Fix |
|------------|---------|-----|
| **Non-Sendable type crossing actor boundary** | `Capture of 'nonSendable' with non-sendable type 'MyClass'` | 1. Make type Sendable<br>2. Use `sending` parameter<br>3. Copy/transform to Sendable type |
| **Actor-isolated property referenced from non-isolated** | `Actor-isolated property 'items' can not be referenced from a non-isolated context` | 1. Add `await`<br>2. Move code to actor<br>3. Make property `nonisolated` |
| **Call to main actor-isolated from non-isolated** | `Call to main actor-isolated instance method 'updateUI()' in a synchronous nonisolated context` | 1. Add `@MainActor` to caller<br>2. Use `await MainActor.run { }`<br>3. Make method `nonisolated` |
| **Mutation of captured var** | `Mutation of captured var 'counter' in concurrently-executing code` | 1. Use actor for state<br>2. Make immutable<br>3. Use `Mutex` (Swift 6.1+) |
| **Sendable closure captures non-Sendable** | `Capture of 'self' with non-sendable type 'ViewController?' in a `@Sendable` closure` | 1. Use `[weak self]`<br>2. Make type Sendable<br>3. Extract needed values before closure |

### Detailed Error Solutions

#### 1. Non-Sendable Type Errors

```swift
// ❌ Error: Non-Sendable type 'UIImage' crossing actor boundary
class ImageProcessor {
    func process(image: UIImage) async {
        Task.detached {
            // Error: capture of 'image' with non-sendable type
            manipulate(image)
        }
    }
}

// ✅ Solution 1: Use sending parameter
class ImageProcessor {
    func process(image: sending UIImage) async {
        Task.detached {
            manipulate(image) // Ownership transferred
        }
    }
}

// ✅ Solution 2: Convert to Sendable representation
class ImageProcessor {
    func process(image: UIImage) async {
        let imageData = image.pngData()! // Data is Sendable
        Task.detached {
            let recreated = UIImage(data: imageData)!
            manipulate(recreated)
        }
    }
}
```

#### 2. Actor Isolation Errors

```swift
// ❌ Error: Actor-isolated property accessed without await
actor DataStore {
    var items: [Item] = []
    
    nonisolated func getItemCount() -> Int {
        items.count // Error: actor-isolated property
    }
}

// ✅ Solution 1: Make method async
actor DataStore {
    var items: [Item] = []
    
    func getItemCount() async -> Int {
        items.count // OK: implicitly isolated to actor
    }
}

// ✅ Solution 2: Use computed property
actor DataStore {
    private var items: [Item] = []
    
    var itemCount: Int {
        items.count // OK: computed property is isolated
    }
}
```

#### 3. MainActor Isolation Errors

```swift
// ❌ Error: Call to MainActor-isolated from background
func backgroundWork() {
    updateUI() // Error: MainActor-isolated
}

@MainActor
func updateUI() { }

// ✅ Solution 1: Make caller MainActor
@MainActor
func backgroundWork() async {
    await fetchData()
    updateUI() // OK: both on MainActor
}

// ✅ Solution 2: Explicit MainActor.run
func backgroundWork() async {
    let data = await fetchData()
    await MainActor.run {
        updateUI()
    }
}
```

## Performance Tuning

### Task Creation Overhead

```swift
// ❌ Excessive task creation
for item in items {
    Task {
        await process(item) // Creates N unstructured tasks
    }
}

// ✅ Use TaskGroup for batch operations
await withTaskGroup(of: Void.self) { group in
    for item in items {
        group.addTask {
            await process(item) // Structured, limited concurrency
        }
    }
}

// ✅ Or use concurrent forEach
await items.concurrentForEach { item in
    await process(item)
}
```

### Clock APIs for Efficient Timing

```swift
// ❌ Old-style sleep
Task {
    Thread.sleep(forTimeInterval: 1.0) // Blocks thread
}

// ❌ Task.sleep with nanoseconds
Task {
    try await Task.sleep(nanoseconds: 1_000_000_000)
}

// ✅ Modern Clock-based approach
let clock = ContinuousClock()
try await clock.sleep(for: .seconds(1))

// ✅ Measure elapsed time
let elapsed = await clock.measure {
    await expensiveOperation()
}
print("Operation took: \(elapsed)")

// ✅ Custom clock for testing
struct TestClock: Clock {
    var now: Instant { .init() }
    func sleep(until deadline: Instant) async throws {
        // Instant return for tests
    }
}
```

### Structured vs Unstructured Tasks

```swift
// ❌ Unstructured tasks lose context
class Service {
    func startBackgroundWork() {
        Task {
            await longRunningWork() // No cancellation propagation
        }
    }
}

// ✅ Structured tasks with proper lifecycle
class Service {
    private var workTask: Task<Void, Never>?
    
    func startBackgroundWork() {
        workTask = Task {
            try await withTaskCancellationHandler {
                await longRunningWork()
            } onCancel: {
                cleanup()
            }
        }
    }
    
    func stopWork() {
        workTask?.cancel()
    }
}

// ✅ Detached tasks only for true daemons
Task.detached(priority: .background) {
    // Long-lived background monitoring
    while !Task.isCancelled {
        await checkSystemHealth()
        try await Task.sleep(for: .minutes(5))
    }
}
```

### Actor Contention Optimization

```swift
// ❌ High contention on single actor
actor Counter {
    private var value = 0
    
    func increment() {
        value += 1
    }
}

// ✅ Reduce contention with batching
actor Counter {
    private var value = 0
    
    func increment(by amount: Int = 1) {
        value += amount
    }
    
    func batchIncrement(_ operations: [Int]) {
        value += operations.reduce(0, +)
    }
}

// ✅ Or use sharding for high throughput
actor ShardedCounter {
    private var shards: [Int]
    
    init(shardCount: Int = ProcessInfo.processInfo.activeProcessorCount) {
        self.shards = Array(repeating: 0, count: shardCount)
    }
    
    func increment() {
        let shard = Int.random(in: 0..<shards.count)
        shards[shard] += 1
    }
    
    var total: Int {
        shards.reduce(0, +)
    }
}
```

## Swift 6.1+ Roadmap

### Swift 6.1 (Shipped)

#### SE-0431: @isolated(any) Function Types
```swift
// Function types that preserve isolation
typealias IsolatedHandler = @isolated(any) () async -> Void

func withIsolation(_ handler: IsolatedHandler) async {
    await handler() // Maintains caller's isolation
}

// Use with actors
actor MyActor {
    func doWork() async {
        await withIsolation {
            // Runs on MyActor
            print("Isolated to: \(self)")
        }
    }
}
```

#### SE-0433: Synchronous Mutual Exclusion Lock (Mutex)
```swift
import Synchronization

// For protecting critical sections without async
final class Statistics: Sendable {
    private let mutex = Mutex<Stats>(.init())
    
    func record(value: Double) {
        mutex.withLock { stats in
            stats.count += 1
            stats.sum += value
        }
    }
    
    var average: Double {
        mutex.withLock { stats in
            stats.count > 0 ? stats.sum / Double(stats.count) : 0
        }
    }
}

private struct Stats {
    var count = 0
    var sum = 0.0
}
```

### Swift 6.2 (In Development)

#### SE-0461: Isolated Default Arguments
```swift
// Default values can use isolation context
@MainActor
class ViewModel {
    // ✅ Default can access MainActor state
    func configure(
        title: String = defaultTitle // Coming in 6.2
    ) { }
    
    @MainActor
    static var defaultTitle: String { "Default" }
}
```

### Future Proposals Under Review

1. **SE-0449**: Allow nonisolated to prevent global actor inference
2. **SE-0450**: Limiting actor isolation inference
3. **SE-0451**: Isolated synchronous deinit
4. **Typed Throws in Concurrency**: Better error propagation
5. **Custom Executors v2**: More control over task execution

### Migration Timeline

| Version | Key Features | Migration Impact |
|---------|--------------|------------------|
| Swift 6.0 | Strict concurrency by default | Major - requires code updates |
| Swift 6.1 | Mutex, @isolated(any) | Minor - additive features |
| Swift 6.2 | Isolated defaults, deinit | Minor - quality of life |
| Swift 7.0 | Custom executors v2 | TBD - performance focused |

Stay updated: [Swift Evolution Dashboard](https://www.swift.org/swift-evolution/)

## Resources

### Official Documentation
- [Swift.org - Concurrency](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/)
- [Migrating to Swift 6](https://www.swift.org/migration/documentation/migrationguide/)
- [Data Race Safety](https://www.swift.org/migration/documentation/swift-6-concurrency-migration-guide/dataracesafety/)

### Swift Evolution Proposals
- [SE-0401: Remove Actor Isolation Inference](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0401-remove-property-wrapper-isolation.md)
- [SE-0414: Region-based Isolation](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0414-region-based-isolation.md)
- [SE-0420: Inheritance of Actor Isolation](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0420-inheritance-of-actor-isolation.md)
- [SE-0430: Sending Parameter Values](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0430-transferring-parameters-and-results.md)

### WWDC Sessions
- **WWDC 2024**: "Migrate your app to Swift 6" - Practical migration guide
- **WWDC 2022**: "Eliminate data races using Swift Concurrency" - Foundational concepts
- **WWDC 2021**: "Meet async/await in Swift" - Introduction to Swift concurrency

### Community Resources
- [Swift Forums - Concurrency](https://forums.swift.org/c/swift-evolution/concurrency/23)
- [Concurrency Index Thread](https://developer.apple.com/forums/thread/768776)
- [Swift Package Index](https://swiftpackageindex.com) - Shows "Safe from data races" badge

## Conclusion

Swift 6's strict concurrency represents a paradigm shift in how we write concurrent code. By embracing:
- Complete data race safety at compile time
- Clear isolation boundaries with actors
- Explicit Sendable requirements
- Progressive migration strategies

We can write safer, more maintainable concurrent code. The compiler becomes our partner in preventing entire classes of bugs, leading to more reliable applications.

Remember: The goal isn't just to silence compiler warnings, but to design systems with clear concurrency boundaries that are easier to reason about and maintain over time.

---

### Apple Docs Swift Testing Playbook (`apple/docs/swift-testing-playbook.md`)

---
description: Comprehensive guide for migrating from XCTest to Swift Testing with best practices from WWDC 2024
globs: "**/*Tests.swift, **/*Test.swift"
alwaysApply: false
---

# The Ultimate Swift Testing Playbook (2024 WWDC Edition, expanded with Apple docs from June 2025)
https://developer.apple.com/xcode/swift-testing/

A hands-on, comprehensive guide for migrating from XCTest to Swift Testing and mastering the new framework. This playbook integrates the latest patterns and best practices from WWDC 2024 and official Apple documentation to make your tests more powerful, expressive, and maintainable.

---

## **1. Migration & Tooling Baseline**

Ensure your environment is set up for a smooth, gradual migration.

| What | Why |
|---|---|
| **Xcode 16 & Swift 6** | Swift Testing is bundled with the latest toolchain. It leverages modern Swift features like macros, structured concurrency, and powerful type-system checks. |
| **Keep XCTest Targets** | **Incremental Migration is Key.** You can have XCTest and Swift Testing tests in the same target, allowing you to migrate file-by-file without breaking CI. Both frameworks can coexist. |
| **Enable Parallel Execution**| In your Test Plan, ensure "Use parallel execution" is enabled. Swift Testing runs tests in parallel by default, which dramatically speeds up test runs and helps surface hidden state dependencies that serial execution might miss. |

### Migration Action Items
- [ ] Ensure all developer machines and CI runners are on macOS 15+ and Xcode 16+.
- [ ] For projects supporting Linux/Windows, add the `swift-testing` SPM package to your `Package.swift`. It's bundled in Xcode and not needed for Apple platforms.
- [ ] For **existing test targets**, you must explicitly enable the framework. In the target's **Build Settings**, find **Enable Testing Frameworks** and set its value to **Yes**. Without this, `import Testing` will fail.
- [ ] In your primary test plan, confirm that **“Use parallel execution”** is enabled. This is the default and recommended setting.

---

## **2. Expressive Assertions: `#expect` & `#require`**

Replace the entire `XCTAssert` family with two powerful, expressive macros. They accept regular Swift expressions, eliminating the need for dozens of specialized `XCTAssert` functions.

| Macro | Use Case & Behavior |
|---|---|
| **`#expect(expression)`** | **Soft Check.** Use for most validations. If the expression is `false`, the issue is recorded, but the test function continues executing. This allows you to find multiple failures in a single run. |
| **`#require(expression)`**| **Hard Check.** Use for critical preconditions (e.g., unwrapping an optional). If the expression is `false` or throws, the test is immediately aborted. This prevents cascading failures from an invalid state. |

### Power Move: Visual Failure Diagnostics
Unlike `XCTAssert`, which often only reports that a comparison failed, `#expect` shows you the exact values that caused the failure, directly in the IDE and logs. This visual feedback is a massive productivity boost.

**Code:**
```swift
@Test("User count meets minimum requirement")
func testUserCount() {
    let userCount = 5
    // This check will fail
    #expect(userCount > 10)
}
```

**Failure Output in Xcode:**
```
▽ Expected expression to be true
#expect(userCount > 10)
      |         | |
      5         | 10
                false
```

### Power Move: Optional-Safe Unwrapping
`#require` is the new, safer replacement for `XCTUnwrap`. It not only checks for `nil` but also unwraps the value for subsequent use.

**Before: The XCTest Way**
```swift
// In an XCTestCase subclass...
func testFetchUser_XCTest() async throws {
    let user = try XCTUnwrap(await fetchUser(id: "123"), "Fetching user should not return nil")
    XCTAssertEqual(user.id, "123")
}
```

**After: The Swift Testing Way**
```swift
@Test("Fetching a valid user succeeds")
func testFetchUser() async throws {
    // #require both checks for nil and unwraps `user` in one step.
    // If fetchUser returns nil, the test stops here and fails.
    let user = try #require(await fetchUser(id: "123"))

    // `user` is now a non-optional User, ready for further assertions.
    #expect(user.id == "123")
    #expect(user.age == 37)
}
```

### Common Assertion Conversions Quick-Reference

Use this table as a cheat sheet when migrating your `XCTest` assertions.

| XCTest Assertion | Swift Testing Equivalent | Notes |
|---|---|---|
| `XCTAssert(expr)` | `#expect(expr)` | Direct replacement for a boolean expression. |
| `XCTAssertEqual(a, b)` | `#expect(a == b)` | Use the standard `==` operator. |
| `XCTAssertNotEqual(a, b)`| `#expect(a != b)` | Use the standard `!=` operator. |
| `XCTAssertNil(a)` | `#expect(a == nil)` | Direct comparison to `nil`. |
| `XCTAssertNotNil(a)` | `#expect(a != nil)` | Direct comparison to `nil`. |
| `XCTAssertTrue(a)` | `#expect(a)` | No change needed if `a` is already a Bool. |
| `XCTAssertFalse(a)` | `#expect(!a)` | Use the `!` operator to negate the expression. |
| `XCTAssertGreaterThan(a, b)` | `#expect(a > b)` | Use any standard comparison operator: `>`, `<`, `>=`, `<=` |
| `XCTUnwrap(a)` | `try #require(a)` | The preferred, safer way to unwrap optionals. |
| `XCTAssertThrowsError(expr)` | `#expect(throws: Error.self) { expr }` | The basic form for checking any error. |
| `XCTAssertNoThrow(expr)` | `#expect(throws: Never.self) { expr }` | The explicit way to assert that no error is thrown. |

### Action Items
- [ ] Run `grep -R "XCTAssert" .` to find all legacy assertions.
- [ ] Convert `XCTUnwrap` calls to `try #require()`. This is a direct and superior replacement.
- [ ] Convert most `XCTAssert` calls to `#expect()`. Use `#require()` only for preconditions where continuing the test makes no sense.
- [ ] For multiple related checks on the same object, use separate `#expect()` statements. Each will be evaluated independently and all failures will be reported.

---

## **3. Setup, Teardown, and State Lifecycle**

Swift Testing replaces `setUpWithError` and `tearDownWithError` with a more natural, type-safe lifecycle using `init()` and `deinit`.

**The Core Concept:** A fresh, new instance of the test suite (`struct` or `class`) is created for **each** test function it contains. This is the cornerstone of test isolation, guaranteeing that state from one test cannot leak into another.

| Method | Replaces... | Behavior |
|---|---|---|
| `init()` | `setUpWithError()` | The initializer for your suite. Put all setup code here. It can be `async` and `throws`. |
| `deinit` | `tearDownWithError()` | The deinitializer. Put cleanup code here. It runs automatically after each test. **Note:** `deinit` is only available on `class` or `actor` suite types, not `struct`s. This is a common reason to choose a class for your suite. |

### Practical Example: Migrating a Database Test Suite

**Before: The XCTest Way**
```swift
final class DatabaseServiceXCTests: XCTestCase {
    var sut: DatabaseService!
    var tempDirectory: URL!

    override func setUpWithError() throws {
        try super.setUpWithError()
        tempDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true)
        
        let testDatabase = TestDatabase(storageURL: tempDirectory)
        sut = DatabaseService(database: testDatabase)
    }

    override func tearDownWithError() throws {
        try FileManager.default.removeItem(at: tempDirectory)
        sut = nil
        tempDirectory = nil
        try super.tearDownWithError()
    }

    func testSavingUser() throws {
        let user = User(id: "user-1", name: "Alex")
        try sut.save(user)
        let loadedUser = try sut.loadUser(id: "user-1")
        XCTAssertNotNil(loadedUser)
    }
}
```

**After: The Swift Testing Way (using `class` for `deinit`)**
```swift
@Suite final class DatabaseServiceTests {
    // Using a class here to demonstrate `deinit` for cleanup.
    let sut: DatabaseService
    let tempDirectory: URL

    init() throws {
        // ARRANGE: Runs before EACH test in this suite.
        self.tempDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true)
        
        let testDatabase = TestDatabase(storageURL: tempDirectory)
        self.sut = DatabaseService(database: testDatabase)
    }
    
    deinit {
        // TEARDOWN: Runs after EACH test.
        try? FileManager.default.removeItem(at: tempDirectory)
    }

    @Test func testSavingUser() throws {
        let user = User(id: "user-1", name: "Alex")
        try sut.save(user)
        #expect(try sut.loadUser(id: "user-1") != nil)
    }
}
```

### Action Items
- [ ] Convert test classes from `XCTestCase` to `struct`s (preferred for automatic state isolation) or `final class`es.
- [ ] Move `setUpWithError` logic into the suite's `init()`.
- [ ] Move `tearDownWithError` logic into the suite's `deinit` (and use a `class` or `actor` if needed).
- [ ] Define the SUT and its dependencies as `let` properties, initialized in `init()`.

---

## **4. Mastering Error Handling**

Go beyond `do/catch` with a dedicated, expressive API for validating thrown errors.

| Overload | Replaces... | Example & Use Case |
|---|---|---|
| **`#expect(throws: Error.self)`**| Basic `XCTAssertThrowsError` | Verifies that *any* error was thrown. |
| **`#expect(throws: BrewingError.self)`** | Typed `XCTAssertThrowsError` | Ensures an error of a specific *type* is thrown. |
| **`#expect(throws: BrewingError.outOfBeans)`**| Specific Error `XCTAssertThrowsError`| Validates a specific error *value* is thrown. |
| **`#expect(throws: ... ) catch: { ... }`** | `do/catch` with `switch` | **Payload Introspection.** The ultimate tool for errors with associated values. It gives you a closure to inspect the thrown error. <br> ```swift #expect(throws: BrewingError.self) { try brew(beans: 0) } catch: { error in guard case let .notEnoughBeans(needed) = error else { Issue.record("Wrong error case thrown"); return } #expect(needed > 0) } ``` |
| **`#expect(throws: Never.self)`** | `XCTAssertNoThrow` | Explicitly asserts that a function does *not* throw. Ideal for happy-path tests. |

---

## **5. Parameterized Tests: Drastically Reduce Boilerplate**

Run a single test function with multiple argument sets to maximize coverage with minimal code. This is superior to a `for-in` loop because each argument set runs as an independent test, can be run in parallel, and failures are reported individually.

| Pattern | How to Use It & When |
|---|---|
| **Single Collection** | `@Test(arguments: [0, 100, -40])` <br> The simplest form. Pass a collection of inputs. |
| **Zipped Collections** | `@Test(arguments: zip(inputs, expectedOutputs))` <br> The most common and powerful pattern. Use `zip` to pair inputs and expected outputs, ensuring a one-to-one correspondence. |
| **Multiple Collections** | `@Test(arguments: ["USD", "EUR"], [1, 10, 100])` <br> **⚠️ Caution: Cartesian Product.** This creates a test case for *every possible combination* of arguments. Use it deliberately when you need to test all combinations. |

### Example: Migrating Repetitive Tests to a Parameterized One

**Before: The XCTest Way**
```swift
func testFlavorVanillaContainsNoNuts() {
    let flavor = Flavor.vanilla
    XCTAssertFalse(flavor.containsNuts)
}
func testFlavorPistachioContainsNuts() {
    let flavor = Flavor.pistachio
    XCTAssertTrue(flavor.containsNuts)
}
func testFlavorChocolateContainsNoNuts() {
    let flavor = Flavor.chocolate
    XCTAssertFalse(flavor.containsNuts)
}
```

**After: The Swift Testing Way using `zip`**
```swift
@Test("Flavor nut content is correct", arguments: zip(
    [Flavor.vanilla, .pistachio, .chocolate],
    [false, true, false]
))
func testFlavorContainsNuts(flavor: Flavor, expected: Bool) {
    #expect(flavor.containsNuts == expected)
}
```

---

## **6. Conditional Execution & Skipping**

Dynamically control which tests run based on feature flags, environment, or known issues.

| Trait | What It Does & How to Use It |
|---|---|
| **`.disabled("Reason")`** | **Unconditionally skips a test.** The test is not run, but it is still compiled. Always provide a descriptive reason for CI visibility (e.g., `"Flaky on CI, see FB12345"`). |
| **`.enabled(if: condition)`** | **Conditionally runs a test.** The test only runs if the boolean `condition` is `true`. This is perfect for tests tied to feature flags or specific environments. <br> ```swift @Test(.enabled(if: FeatureFlags.isNewAPIEnabled)) func testNewAPI() { /* ... */ } ``` |
| **`@available(...)`** | **OS Version-Specific Tests.** Apply this attribute directly to the test function. It's better than a runtime `#available` check because it allows the test runner to know the test is skipped for platform reasons, which is cleaner in test reports. |

---

## **7. Specialized Assertions for Clearer Failures**

While `#expect(a == b)` works, purpose-built patterns provide sharper, more actionable failure messages by explaining *why* something failed, not just *that* it failed.

> **⚠️ Note:** Swift Testing is still evolving and doesn't have all the specialized assertion APIs that XCTest provides. Some common patterns require manual implementation or third-party libraries like Swift Numerics.

| Assertion Type | Why It's Better Than a Generic Check |
| :--- | :--- |
| **Comparing Collections (Unordered)**<br>Use Set comparison for order-independent equality | A simple `==` check on arrays fails if elements are the same but the order is different. Converting to Sets ignores order, preventing false negatives for tests where order doesn't matter. <br><br> **Brittle:** `#expect(tags == ["ios", "swift"])` <br> **Robust:** `#expect(Set(tags) == Set(["swift", "ios"]))` |
| **Floating-Point Accuracy**<br>Use manual tolerance checks or Swift Numerics | Floating-point math is imprecise. `#expect(0.1 + 0.2 == 0.3)` will fail. Use manual tolerance checking or Swift Numerics for robust floating-point comparisons. <br><br> **Fails:** `#expect(result == 0.3)` <br> **Passes:** `#expect(abs(result - 0.3) < 0.0001)` <br> **With Swift Numerics:** `#expect(result.isApproximatelyEqual(to: 0.3, absoluteTolerance: 0.0001))` |

---

## **8. Structure and Organization at Scale**

Use suites and tags to manage large and complex test bases.

### Suites and Nested Suites
A `@Suite` groups related tests and can be nested for a clear hierarchy. Traits applied to a suite are inherited by all tests and nested suites within it.

### Tags for Cross-Cutting Concerns
Tags associate tests with common characteristics (e.g., `.network`, `.ui`, `.regression`) regardless of their suite. This is invaluable for filtering.

1.  **Define Tags in a Central File:**
    ```swift
    // /Tests/Support/TestTags.swift
    import Testing

    extension Tag {
        @Tag static var fast: Self
        @Tag static var regression: Self
        @Tag static var flaky: Self
        @Tag static var networking: Self
    }
    ```
2.  **Apply Tags & Filter:**
    ```swift
    // Apply to a test or suite
    @Test("Username validation", .tags(.fast, .regression))
    func testUsername() { /* ... */ }

    // Run from CLI
    // swift test --filter .fast
    // swift test --skip .flaky
    // swift test --filter .networking --filter .regression

    // Filter in Xcode Test Plan
    // Add "fast" to the "Include Tags" field or "flaky" to the "Exclude Tags" field.
    ```
### Power Move: Xcode UI Integration for Tags
Xcode 16 deeply integrates with tags, turning them into a powerful organizational tool.

-   **Grouping by Tag in Test Navigator:** In the Test Navigator (`Cmd-6`), click the tag icon at the top. This switches the view from the file hierarchy to one where tests are grouped by their tags. It's a fantastic way to visualize and run all tests related to a specific feature.
-   **Test Report Insights:** After a test run, the Test Report can automatically find patterns. Go to the **Insights** tab to see messages like **"All 7 tests with the 'networking' tag failed."** This immediately points you to systemic issues, saving significant debugging time.

---

## **9. Concurrency and Asynchronous Testing**

### Async/Await and Confirmations
- **Async Tests**: Simply mark your test function `async` and use `await`.
- **Confirmations**: To test APIs with completion handlers or that fire multiple times (like delegates or notifications), use `confirmation`.
- **`fulfillment(of:timeout:)`**: This is the global function you `await` to pause the test until your confirmations are fulfilled or a timeout is reached.

```swift
@Test("Delegate is notified 3 times")
async func testDelegateNotifications() async throws {
    // Create a confirmation that expects to be fulfilled exactly 3 times.
    let confirmation = confirmation("delegate.didUpdate was called", expectedCount: 3)
    let delegate = MockDelegate { await confirmation.fulfill() }
    let sut = SystemUnderTest(delegate: delegate)

    sut.performActionThatNotifiesThreeTimes()
    
    // Explicitly wait for the confirmation to be fulfilled with a 1-second timeout.
    try await fulfillment(of: [confirmation], timeout: .seconds(1))
}
```

### Advanced Asynchronous Patterns

#### Asserting an Event Never Happens
Use a confirmation with `expectedCount: 0` to verify that a callback or delegate method is *never* called during an operation. If `fulfill()` is called on it, the test will fail.

```swift
@Test("Logging out does not trigger a data sync")
async func testLogoutDoesNotSync() async throws {
    let syncConfirmation = confirmation("data sync was triggered", expectedCount: 0)
    let mockSyncEngine = MockSyncEngine { await syncConfirmation.fulfill() }
    let sut = AccountManager(syncEngine: mockSyncEngine)
    
    sut.logout()
    
    // The test passes if the confirmation is never fulfilled within the timeout.
    // If it *is* fulfilled, this will throw an error and fail the test.
    await fulfillment(of: [syncConfirmation], timeout: .seconds(0.5), performing: {})
}
```

#### Bridging Legacy Completion Handlers
For older asynchronous code that uses completion handlers, use `withCheckedThrowingContinuation` to wrap it in a modern `async/await` call that Swift Testing can work with.

```swift
func legacyFetch(completion: @escaping (Result<Data, Error>) -> Void) {
    // ... legacy async code ...
}

@Test func testLegacyFetch() async throws {
    let data = try await withCheckedThrowingContinuation { continuation in
        legacyFetch { result in
            continuation.resume(with: result)
        }
    }
    #expect(!data.isEmpty)
}
```

### Controlling Parallelism
- **`.serialized`**: Apply this trait to a `@Test` or `@Suite` to force its contents to run serially (one at a time). Use this as a temporary measure for legacy tests that are not thread-safe or have hidden state dependencies. The goal should be to refactor them to run in parallel.
- **`.timeLimit`**: A safety net to prevent hung tests from stalling CI. The more restrictive (shorter) duration wins when applied at both the suite and test level.

---

## **10. Advanced API Cookbook**

| Feature | What it Does & How to Use It |
|---|---|
| **`withKnownIssue`** | Marks a test as an **Expected Failure**. It's better than `.disabled` for known bugs. The test still runs but won't fail the suite. Crucially, if the underlying bug gets fixed and the test *passes*, `withKnownIssue` will fail, alerting you to remove it. |
| **`CustomTestStringConvertible`** | Provides custom, readable descriptions for your types in test failure logs. Conform your key models to this protocol to make debugging much easier. |
| **`.bug("JIRA-123")` Trait** | Associates a test directly with a ticket in your issue tracker. This adds invaluable context to test reports in Xcode and Xcode Cloud. |
| **`Test.current`** | A static property (`Test.current`) that gives you runtime access to the current test's metadata, such as its name, tags, and source location. Useful for advanced custom logging. |
| **Multiple Expectations Pattern** | Use separate `#expect()` statements for validating multiple properties. Each expectation is evaluated independently, and all failures are reported even if earlier ones fail. This provides comprehensive feedback about object state. <br><br> ```swift let user = try #require(loadUser()) #expect(user.name == "John") #expect(user.age >= 18) #expect(user.isActive) ``` |

---

## **11. Common Pitfalls and How to Avoid Them**

A checklist of common mistakes developers make when adopting Swift Testing.

1.  **Overusing `#require()`**
    -   **The Pitfall:** Using `#require()` for every check. This makes tests brittle and hides information. If the first `#require()` fails, the rest of the test is aborted, and you won't know if other things were also broken.
    -   **The Fix:** Use `#expect()` for most checks. Only use `#require()` for essential setup conditions where the rest of the test would be nonsensical if they failed (e.g., a non-nil SUT, a valid URL).

2.  **Forgetting State is Isolated**
    -   **The Pitfall:** Assuming that a property modified in one test will retain its value for the next test in the same suite.
    -   **The Fix:** Remember that a **new instance** of the suite is created for every test. This is a feature, not a bug! All shared setup must happen in `init()`. Do not rely on state carrying over between tests.

3.  **Accidentally Using a Cartesian Product**
    -   **The Pitfall:** Passing multiple collections to a parameterized test without `zip`, causing an exponential explosion of test cases (`@Test(arguments: collectionA, collectionB)`).
    -   **The Fix:** Be deliberate. If you want one-to-one pairing, **always use `zip`**. Only use the multi-collection syntax when you explicitly want to test every possible combination.

4.  **Ignoring the `.serialized` Trait for Unsafe Tests**
    -   **The Pitfall:** Migrating old, stateful tests that are not thread-safe and seeing them fail randomly due to parallel execution.
    -   **The Fix:** As a temporary measure, apply the `.serialized` trait to the suite containing these tests. This forces them to run one-at-a-time, restoring the old behavior. The long-term goal should be to refactor the tests to be parallel-safe and remove the trait.

---

## **12. Migrating from XCTest**

Swift Testing and XCTest can coexist in the same target, enabling an incremental migration.

### Key Differences at a Glance

| Feature | XCTest | Swift Testing |
|---|---|---|
| **Test Discovery** | Method name must start with `test...` | `@Test` attribute on any function or method. |
| **Suite Type** | `class MyTests: XCTestCase` | `struct MyTests` (preferred), `class`, or `actor`. |
| **Assertions** | `XCTAssert...()` family of functions | `#expect()` and `#require()` macros with Swift expressions. |
| **Error Unwrapping** | `try XCTUnwrap(...)` | `try #require(...)` |
| **Setup/Teardown**| `setUpWithError()`, `tearDownWithError()` | `init()`, `deinit` (on classes/actors) |
| **Asynchronous Wait**| `XCTestExpectation` | `confirmation()` and `await fulfillment(of:timeout:)` |
| **Parallelism** | Opt-in, multi-process | Opt-out, in-process via Swift Concurrency. |

### What NOT to Migrate (Yet)
Continue using XCTest for the following, as they are not currently supported by Swift Testing:
- **UI Automation Tests** (using `XCUIApplication`)
- **Performance Tests** (using `XCTMetric` and `measure { ... }`)
- **Tests written in Objective-C**

---

## **13. Swift 6.2 Testing Enhancements (2025 Edition)**

Swift 6.2 introduces powerful new testing capabilities that take Swift Testing to the next level. These features provide enhanced debugging context, process lifecycle testing, and more sophisticated test control.

### 13.1 Exit Tests: Testing Process Lifecycle and Crashes

Exit tests allow you to verify that code properly handles process termination scenarios, crash recovery, and subprocess management. This is crucial for applications that spawn external processes or need robust crash recovery.

| Feature | Use Case | Benefits |
|---|---|---|
| **`#expect(processExitsWith: .success)`** | Test that processes terminate cleanly | Verifies graceful shutdown and cleanup |
| **`#expect(processExitsWith: .failure)`** | Test that invalid operations fail appropriately | Ensures proper error handling and exit codes |
| **Process Recovery Testing** | Test auto-restart and crash recovery logic | Validates resilience mechanisms |

#### Practical Example: Server Process Lifecycle

```swift
@Test("Server handles unexpected termination gracefully", .tags(.exitTests))
func serverCrashRecovery() async throws {
    await #expect(processExitsWith: .success) {
        let serverManager = ServerManager.shared
        
        // Start server process
        await serverManager.start()
        
        // Simulate unexpected termination
        if let server = serverManager.bunServer {
            server.terminate()
        }
        
        // Wait for auto-restart logic
        try await Task.sleep(for: .milliseconds(2_000))
        
        // Verify recovery behavior
        // Test should verify that the system handles the crash gracefully
        
        // Clean shutdown
        await serverManager.stop()
    }
}
```

#### CLI Tool Process Validation

```swift
@Test("Shell command executes successfully", .tags(.exitTests))
func shellCommandExecution() async throws {
    await #expect(processExitsWith: .success) {
        let process = Process()
        process.executableURL = URL(fileURLWithPath: "/bin/sh")
        process.arguments = ["-c", "ls /tmp | head -5"]
        
        try process.run()
        process.waitUntilExit()
        
        guard process.terminationStatus == 0 else {
            throw ProcessError.commandFailed(process.terminationStatus)
        }
    }
}
```

### 13.2 Attachments: Rich Debugging Context

Attachments revolutionize test debugging by capturing system state, configuration details, and diagnostic information when tests fail. This provides invaluable context that makes debugging faster and more effective.

| Attachment Type | Best For | Example Use Cases |
|---|---|---|
| **System Information** | Environment debugging | OS version, memory, processor info |
| **Configuration State** | Settings and setup issues | Server ports, network config, feature flags |
| **Process Details** | Service lifecycle issues | PIDs, running state, error messages |
| **Performance Metrics** | Performance regression analysis | Timing data, memory usage, throughput |

#### Enhanced Test with Diagnostic Attachments

```swift
@Test("Network configuration with full diagnostics", .tags(.attachmentTests))
func networkConfigurationDiagnostics() throws {
    // Attach system environment for context
    Attachment.record("System Info", """
        OS: \(ProcessInfo.processInfo.operatingSystemVersionString)
        Environment: \(ProcessInfo.processInfo.environment["CI"] != nil ? "CI" : "Local")
        Timestamp: \(Date().ISO8601Format())
        """)
    
    // Attach network state
    let localIP = NetworkUtility.getLocalIPAddress()
    let allIPs = NetworkUtility.getAllIPAddresses()
    
    Attachment.record("Network Configuration", """
        Local IP: \(localIP ?? "none")
        All IPs: \(allIPs.joined(separator: ", "))
        Interface Count: \(allIPs.count)
        """)
    
    // Test logic with rich failure context
    #expect(localIP != nil || allIPs.isEmpty)
}
```

#### Performance Testing with Detailed Metrics

```
/* Detailed source-code truncated for AI context efficiency. */
```

### 13.3 Enhanced Conditional Testing with ConditionTrait.evaluate()

Swift 6.2 enhances conditional testing with the ability to check trait conditions outside of test context, enabling smarter test execution based on system capabilities.

#### Custom Condition Traits

```
/* Detailed source-code truncated for AI context efficiency. */
```

#### Smart Test Execution

```swift
// Replace broad .disabled() with intelligent conditions
@Suite("Server Manager Tests", .tags(.serverManager))
struct ServerManagerTests {
    
    @Test("Server lifecycle with full binary", ServerBinaryAvailableCondition(), NotInCICondition())
    func serverLifecycleWithBinary() async throws {
        // This test only runs when:
        // 1. The server binary is actually available
        // 2. We're not in a CI environment
        // This prevents false failures and provides clear skip reasons
    }
    
    @Test("Network configuration", NetworkAvailableCondition())
    func networkConfiguration() throws {
        // Only runs when network interfaces are available
        // Automatically skipped in containerized or network-less environments
    }
}
```

### 13.4 Enhanced Tag System for Better Organization

Expand your tag system to include the new Swift 6.2 testing capabilities:

```swift
// Enhanced TestTags.swift
extension Tag {
    // Existing tags
    @Tag static var critical: Self
    @Tag static var networking: Self
    @Tag static var concurrency: Self
    
    // Swift 6.2 Enhanced Tags
    @Tag static var exitTests: Self          // Tests using processExitsWith
    @Tag static var attachmentTests: Self    // Tests with rich diagnostic attachments
    @Tag static var requiresServerBinary: Self  // Tests needing actual server binary
    @Tag static var requiresNetwork: Self   // Tests needing network interfaces
    @Tag static var processSpawn: Self      // Tests that spawn external processes
    @Tag static var gitRepository: Self     // Tests that need to run in a Git repository
}
```

#### Strategic Test Filtering

```bash
# Run only tests with attachments for debugging
swift test --filter .attachmentTests

# Skip exit tests in CI environments
swift test --skip .exitTests

# Run performance tests with attachments for detailed analysis
swift test --filter .performance --filter .attachmentTests

# Run all tests that require external dependencies
swift test --filter .requiresServerBinary --filter .requiresNetwork

# Run Git repository tests only when in a Git repo
swift test --filter .gitRepository
```

### 13.5 Best Practices for Swift 6.2 Features

#### Exit Tests Guidelines
- **Use Sparingly**: Exit tests are powerful but should be used for critical process lifecycle scenarios
- **Clean State**: Always ensure proper cleanup in exit test blocks
- **Avoid Elevated Permissions**: Don't test operations requiring sudo or admin rights
- **Mock When Possible**: Use mock processes for testing logic without actual system processes

#### Attachment Strategy
- **Contextual Information**: Attach system state, configuration, and environment details
- **Progressive Detail**: Start with summary info, add detailed data as needed
- **Performance Focus**: Use attachments extensively in performance tests for trend analysis
- **Failure Context**: Attach diagnostic info that helps understand why tests failed

#### Condition Trait Design
- **Specific Conditions**: Create focused conditions for specific capabilities
- **Clear Skip Reasons**: Always provide descriptive reasons for skipped tests
- **Environment Awareness**: Consider CI, local development, and different platforms
- **Combine Thoughtfully**: Use multiple conditions when tests have multiple requirements

### Action Items for Swift 6.2 Adoption

- [ ] **Audit Disabled Tests**: Replace broad `.disabled()` with specific condition traits
- [ ] **Add Exit Tests**: Identify critical process lifecycle scenarios and add exit tests
- [ ] **Enhance Debugging**: Add attachments to complex tests and all performance tests
- [ ] **Update Tag Strategy**: Add Swift 6.2 tags and update filtering strategies
- [ ] **Create Condition Traits**: Build reusable conditions for common system requirements
- [ ] **Document Skip Reasons**: Ensure all conditional tests have clear skip explanations
- [ ] **CI Integration**: Update CI scripts to handle new test filtering and skip reasons

---

## **Appendix: Evergreen Testing Principles (The F.I.R.S.T. Principles)**

These foundational principles are framework-agnostic, and Swift Testing is designed to make adhering to them easier than ever.

| Principle | Meaning | Swift Testing Application |
|---|---|---|
| **Fast** | Tests must execute in milliseconds. | Lean on default parallelism. Use `.serialized` sparingly. |
| **Isolated**| Tests must not depend on each other. | Swift Testing enforces this by creating a new suite instance for every test. Random execution order helps surface violations. |
| **Repeatable** | A test must produce the same result every time. | Control all inputs (dates, network responses) with mocks/stubs. Reset state in `init`/`deinit`. |
| **Self-Validating**| The test must automatically report pass or fail. | Use `#expect` and `#require`. Never rely on `print()` for validation. |
| **Timely**| Write tests alongside the production code. | Use parameterized tests (`@Test(arguments:)`) to easily cover edge cases as you write code. |

---

--- METRICS ---
- Files Extracted: 57
- Estimated Token Budget: ~127298 tokens
- Canonical Reference: https://codewiki.google/github.com/amantus-ai/vibetunnel
