vibetunnel/web/snapshot-format.md
Mario Zechner ef7a679c2b Fix binary buffer encoding to support large terminals
The binary encoding was using 16-bit unsigned integers which failed when:
- Terminal has many rows (>65k)
- Cursor is above the viewport (negative relative position)

Changes:
- Upgrade to version 2 of the binary format
- Use 32-bit integers for dimensions and positions
- Use signed integers for viewport/cursor positions
- Update header size from 16 to 32 bytes
- Update documentation to reflect new format

This fixes the issue where cursorY could be negative when the cursor
is above the visible viewport (e.g., cursorY=0, viewportY=46 results
in relative cursorY=-46).

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-20 02:31:15 +02:00

6 KiB

VibeTunnel Terminal Buffer Snapshot Format

This document describes the binary format used for efficient terminal buffer snapshots.

Overview

The snapshot format is a compact binary representation of terminal buffer state, designed to minimize data transfer while preserving all terminal attributes. It consists of a header followed by a stream of encoded cells.

Format Structure

┌──────────────┬─────────────────────────────────┐
│    Header    │         Cell Stream             │
│   (32 bytes) │    (variable, 4+ bytes/cell)   │
└──────────────┴─────────────────────────────────┘

Header Format (32 bytes) - Version 2

Offset  Size  Field         Description
------  ----  ----------    -----------
0x00    2     Magic         0x5654 ("VT" in ASCII)
0x02    1     Version       Format version (0x02 for 32-bit support)
0x03    1     Flags         Reserved for future use
0x04    4     Cols          Terminal width (32-bit unsigned, little-endian)
0x08    4     Rows          Number of rows in this snapshot (32-bit unsigned, little-endian)
0x0C    4     ViewportY     Starting line number in buffer (32-bit signed, little-endian)
0x10    4     CursorX       Cursor column position (32-bit signed, little-endian)
0x14    4     CursorY       Cursor row position relative to viewport (32-bit signed, little-endian)
0x18    4     Reserved      Reserved for future use

Note: CursorY is relative to the viewport and can be negative if the cursor is above the visible area.

Cell Format

Each cell uses a variable-length encoding:

Basic Cell (4 bytes) - ASCII with palette colors

Offset  Size  Field         Description
------  ----  ----------    -----------
0x00    1     Character     UTF-8 character (ASCII range)
0x01    1     Attributes    Bit flags (see below)
0x02    1     FG Color      Foreground palette index (0-255)
0x03    1     BG Color      Background palette index (0-255)

Extended Cell (variable) - Unicode or RGB colors

Offset  Size  Field         Description
------  ----  ----------    -----------
0x00    1     Header        [2 bits: char_len-1][1 bit: rgb_fg][1 bit: rgb_bg][4 bits: reserved]
0x01    1     Attributes    Bit flags (see below)
0x02    1-4   Character     UTF-8 character (length from header)
0x??    1/3   FG Color      1 byte palette or 3 bytes RGB
0x??    1/3   BG Color      1 byte palette or 3 bytes RGB

Attribute Flags (1 byte)

Bit  Flag
---  ----
0    Bold
1    Italic
2    Underline
3    Dim
4    Inverse
5    Invisible
6    Strikethrough
7    Extended (if set, use extended cell format)

Special Encodings

Run-Length Encoding

For repeated cells (common with spaces), use RLE:

0xFF <count:1> <cell:4+>

This encodes up to 255 repeated cells.

Empty Line Marker

For completely empty lines (all spaces with default attributes):

0xFE <count:1>

This encodes up to 255 empty lines.

Color Encoding

Palette Colors (0-255)

Standard xterm 256-color palette indices.

RGB Colors (24-bit)

When RGB flag is set in extended cell header:

R (1 byte) G (1 byte) B (1 byte)

API Endpoint

Request

GET /api/sessions/{sessionId}/buffer?viewportY={Y}&lines={N}

Parameters:

  • sessionId: Session identifier
  • viewportY: Starting line in the terminal buffer (0-based)
  • lines: Number of lines to return

Response

Content-Type: application/octet-stream
Content-Length: {size}

[Binary data as described above]

Example

For a 80x24 terminal showing "Hello" on black background:

Header (32 bytes):
56 54 02 00  50 00 00 00  18 00 00 00  00 00 00 00  05 00 00 00  00 00 00 00  00 00 00 00
│  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │
│  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   └─┴─┴─┴─ Reserved
│  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   └─┴─┴─┴─ CursorY (0)
│  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   └─┴─┴─┴─ CursorX (5)
│  │  │  │   │  │  │  │   │  │  │  │   └─┴─┴─┴─ ViewportY (0)
│  │  │  │   │  │  │  │   └─┴─┴─┴─ Rows (24)
│  │  │  │   └─┴─┴─┴─ Cols (80)
│  │  │  └─ Flags (0)
│  │  └─ Version (2)
└─┴─ Magic "VT"

Cells:
48 00 07 00  65 00 07 00  6C 00 07 00  6C 00 07 00  6F 00 07 00
│  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │
│  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   │  │  │  └─ BG: black (0)
│  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   │  │  └─ FG: white (7)
│  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   │  └─ Attributes: none (0)
│  │  │  │   │  │  │  │   │  │  │  │   │  │  │  │   └─ Character: 'o'
[Continue for remaining cells...]

FF 4B 20 00 07 00  (RLE: 75 spaces)
FE 17              (23 empty lines)

Implementation Notes

  1. The server maintains xterm.js Terminal instances for each active session
  2. Binary encoding happens on-the-fly when buffer endpoint is called
  3. Client can request specific viewport regions for efficient updates
  4. Format is designed to be easily parseable with minimal overhead
  5. Extended format allows for future enhancements without breaking compatibility

Performance Characteristics

  • Basic ASCII terminal: ~4 bytes per cell
  • With colors/attributes: ~4-7 bytes per cell
  • With RLE compression: ~10-20% of original size for typical terminals
  • Network transfer: ~3-8KB for full 80x24 screen (before gzip)