# Minga Port Protocol Specification

The BEAM editor core and rendering frontends communicate over a binary protocol on stdin/stdout of each frontend process. All frontends (Swift/Metal on macOS, GTK4 on Linux, Zig/libvaxis TUI) speak the same base protocol. GUI frontends additionally receive structured chrome opcodes documented in [GUI_PROTOCOL.md](GUI_PROTOCOL.md). This document is the authoritative reference for implementing a Minga frontend. You should be able to build a working frontend by reading only this file (plus GUI_PROTOCOL.md for native GUIs).

## Transport

The frontend runs as a child process of the BEAM. Communication uses stdin (BEAM → Frontend) and stdout (Frontend → BEAM).

**Framing:** Every message is prefixed with a 4-byte big-endian unsigned integer indicating the payload length. The payload follows immediately. Erlang's `{:packet, 4}` Port option handles framing on the BEAM side; frontends must read/write the 4-byte length header explicitly.

```
┌──────────────┬────────────────────────┐
│ length (4B)  │ payload (length bytes)  │
│ big-endian   │ opcode (1B) + fields    │
└──────────────┴────────────────────────┘
```

**Batching:** The BEAM may concatenate multiple commands into a single length-prefixed message. The frontend must parse commands sequentially within a payload using `commandSize()` to determine where each command ends. The Elixir encoder typically batches an entire render frame into one message.

**Byte order:** All multi-byte integers are big-endian unless noted otherwise.

**Text encoding:** All text fields (draw_text content, language names, query source) are UTF-8 encoded.

---

## Quick Reference

### BEAM → Frontend (Render Commands)

| Opcode | Name | Size | Description |
|--------|------|------|-------------|
| `0x10` | draw_text | 14 + text_len | Draw styled text at a position |
| `0x11` | set_cursor | 5 | Position the cursor |
| `0x12` | clear | 1 | Clear the entire screen |
| `0x13` | batch_end | 1 | End of frame; flush to screen |
| `0x14` | define_region | 15 | Create/update a layout region |
| `0x15` | set_cursor_shape | 2 | Change cursor appearance |
| `0x16` | set_title | 3 + title_len | Set the window/terminal title |
| `0x18` | clear_region | 3 | Clear a specific region |
| `0x19` | destroy_region | 3 | Remove a region |
| `0x1A` | set_active_region | 3 | Route draw commands to a region |
| `0x27` | measure_text | 7 + text_len | Request display width of text |

### BEAM → Frontend (Config Commands)

| Opcode | Name | Size | Description |
|--------|------|------|-------------|
| `0x50` | set_font | 7 + name_len | Set font family, size, weight, and ligatures |

### BEAM → Frontend (Highlight Commands)

| Opcode | Name | Size | Description |
|--------|------|------|-------------|
| `0x20` | set_language | 3 + name_len | Set the active tree-sitter language |
| `0x21` | parse_buffer | 9 + source_len | Parse buffer content for highlighting |
| `0x22` | set_highlight_query | 5 + query_len | Set a custom highlight query |
| `0x23` | load_grammar | 5 + name_len + path_len | Load a grammar from a shared library |
| `0x24` | set_injection_query | 5 + query_len | Set a custom injection query |
| `0x25` | query_language_at | 9 | Query the language at a byte offset |
| `0x26` | edit_buffer | 7 + variable | Incremental edit deltas |

### Frontend → BEAM (Input Events)

| Opcode | Name | Size | Description |
|--------|------|------|-------------|
| `0x01` | key_press | 6 | A key was pressed |
| `0x02` | resize | 5 | Terminal/window was resized |
| `0x03` | ready | 5 or 13 | Frontend is initialized and ready |
| `0x04` | mouse_event | 9 | Mouse button, wheel, or motion (8-byte legacy also accepted) |
| `0x05` | capabilities_updated | 9 | Updated capabilities after async detection |

### Frontend → BEAM (Highlight Responses)

| Opcode | Name | Size | Description |
|--------|------|------|-------------|
| `0x30` | highlight_spans | 9 + count × 10 | Syntax highlight byte ranges |
| `0x31` | highlight_names | 3 + variable | Capture name list for spans |
| `0x32` | grammar_loaded | 4 + name_len | Grammar load success/failure |
| `0x33` | language_at_response | 7 + name_len | Language at a byte offset |
| `0x34` | injection_ranges | 3 + variable | Injection language regions |
| `0x35` | text_width | 7 | Display width of measured text |
| `0x36` | fold_ranges | variable | Fold range results from tree-sitter |
| `0x37` | indent_result | 13 | Indent level result for a line |
| `0x38` | textobject_result | variable | Text object range result (or nil) |
| `0x39` | textobject_positions | 9 + count × 9 | Proactive text object position cache |
| `0x3B` | request_reparse | 5 | Parser requests full reparse after stale edit deltas |

### Frontend → BEAM (Diagnostics)

| Opcode | Name | Size | Description |
|--------|------|------|-------------|
| `0x60` | log_message | 4 + msg_len | Log message from the frontend |

---

## Render Commands (BEAM → Frontend)

### `0x10` draw_text

Draw styled text at a screen position.

```
opcode:   u8  = 0x10
row:      u16           screen row (0-indexed from top)
col:      u16           screen column (0-indexed from left)
fg:       u24           foreground color (RGB, 0x000000 = terminal default)
bg:       u24           background color (RGB, 0x000000 = terminal default)
attrs:    u8            style attribute flags (see below)
text_len: u16           byte length of text
text:     [text_len]u8  UTF-8 encoded text
```

Total size: 14 + text_len bytes.

**Attribute flags:**

| Flag | Value | Meaning |
|------|-------|---------|
| BOLD | `0x01` | Bold weight |
| UNDERLINE | `0x02` | Single underline |
| ITALIC | `0x04` | Italic style |
| REVERSE | `0x08` | Swap foreground and background |

Multiple flags can be combined with bitwise OR: `0x05` = bold + italic.

**Behavior:** The frontend writes each grapheme cluster in the text to consecutive screen cells starting at `(row, col)`. Wide characters (CJK, emoji) occupy 2 cells. The frontend is responsible for grapheme iteration and display width calculation. Text that extends past the screen width is clipped.

**Color convention:** `0x000000` for fg means "use the terminal's default foreground." `0x000000` for bg means "use the terminal's default background." Actual black can be represented as `0x000001` if needed, though in practice themes avoid this ambiguity.

### `0x11` set_cursor

Position the visible cursor.

```
opcode: u8  = 0x11
row:    u16           screen row
col:    u16           screen column
```

Total size: 5 bytes.

**Behavior:** Move the cursor to the specified position. The cursor is displayed at this location after the next `batch_end` render. Only one cursor position is active at a time; the last `set_cursor` in a frame wins.

### `0x12` clear

Clear the entire screen.

```
opcode: u8 = 0x12
```

Total size: 1 byte.

**Behavior:** Reset all cells to blank (space character, default colors, no attributes). This is always the first command in a render frame.

### `0x13` batch_end

End of a render frame.

```
opcode: u8 = 0x13
```

Total size: 1 byte.

**Behavior:** The frontend flushes all pending draw operations to the screen. For a TUI, this means writing the diff of changed cells to the terminal. For a GUI, this means triggering a display refresh. No visible changes should appear until `batch_end` is received.

### `0x15` set_cursor_shape

Change the cursor's visual appearance.

```
opcode: u8 = 0x15
shape:  u8           cursor shape
```

Total size: 2 bytes.

**Shape values:**

| Value | Shape | Typical Use |
|-------|-------|-------------|
| `0x00` | Block | Normal mode |
| `0x01` | Beam (line) | Insert mode |
| `0x02` | Underline | Replace mode |

### `0x16` set_title

Set the window or terminal title.

```
opcode:    u8  = 0x16
title_len: u16           byte length of title
title:     [title_len]u8 UTF-8 encoded title string
```

Total size: 3 + title_len bytes.

**Behavior:** TUI frontends emit OSC 0 (`\x1b]0;{title}\x07`). GUI frontends set the window title.

---

## Render Frame Lifecycle

Every render frame follows this sequence:

```
clear
draw_text × N       (one per styled text segment, ordered top-to-bottom)
set_cursor           (exactly once)
set_cursor_shape     (exactly once; may be omitted if unchanged)
batch_end            (triggers the actual render)
```

The BEAM sends the entire frame as a single batched message. The frontend processes commands in order and only renders to screen on `batch_end`.

Between frames, the frontend must not modify the screen. The BEAM drives all visual updates.

---

## Input Events (Frontend → BEAM)

### `0x01` key_press

A key was pressed.

```
opcode:    u8  = 0x01
codepoint: u32           Unicode codepoint of the key
modifiers: u8            modifier flags (see below)
```

Total size: 6 bytes.

**Codepoint values:** Standard Unicode codepoints for printable characters. For special keys, use the codepoint values defined by the frontend's input library (e.g., libvaxis uses values above the Unicode range for function keys, arrows, etc.). The BEAM's key handling maps these to editor actions.

**Modifier flags:**

| Flag | Value |
|------|-------|
| SHIFT | `0x01` |
| CTRL | `0x02` |
| ALT | `0x04` |
| SUPER | `0x08` |

Combined with bitwise OR: Ctrl+Shift = `0x03`.

### `0x02` resize

The terminal or window was resized.

```
opcode: u8  = 0x02
width:  u16           new width in columns (or pixels for GUI)
height: u16           new height in rows (or pixels for GUI)
```

Total size: 5 bytes.

**Behavior:** Sent when the frontend detects a size change (SIGWINCH for TUI, window resize event for GUI). The BEAM re-renders to the new dimensions on the next frame.

### `0x03` ready

The frontend has initialized and is ready to receive render commands.

**Short format (5 bytes):**
```
opcode: u8  = 0x03
width:  u16           initial width
height: u16           initial height
```

**Extended format (13 bytes):**
```
opcode:       u8  = 0x03
width:        u16           initial width
height:       u16           initial height
caps_version: u8            capability format version (currently 1)
caps_len:     u8            length of capability data
caps_data:    [caps_len]u8  capability fields (see "Capability Negotiation" section)
```

**Behavior:** Sent exactly once, during startup, after the frontend has set up its rendering surface. The BEAM waits for this event before sending any render commands.

Frontends should use the extended format when possible. The BEAM detects which format was sent by checking the payload length: 5 bytes = short format with default capabilities, 13+ bytes = extended format with explicit capabilities.

### `0x04` mouse_event

A mouse button, wheel, or motion event.

```
opcode:      u8  = 0x04
row:         i16           screen row (signed; -1 = outside window)
col:         i16           screen column (signed; -1 = outside window)
button:      u8            button identifier
modifiers:   u8            modifier flags (same as key_press)
event_type:  u8            type of mouse event
click_count: u8            1 = single, 2 = double, 3 = triple (clamped)
```

Total size: 9 bytes.

**Backward compatibility:** The BEAM decoder accepts 8-byte messages (legacy format without `click_count`) and defaults `click_count` to 1. GUI frontends should always send the 9-byte format with the native click count from the OS. TUI frontends send `click_count=1` and let the BEAM detect multi-clicks via timing.

**Button values:**

| Value | Button |
|-------|--------|
| `0x00` | Left |
| `0x01` | Middle |
| `0x02` | Right |
| `0x03` | None (motion without button) |
| `0x40` | Wheel up |
| `0x41` | Wheel down |
| `0x42` | Wheel right |
| `0x43` | Wheel left |

**Event type values:**

| Value | Type |
|-------|------|
| `0x00` | Press |
| `0x01` | Release |
| `0x02` | Motion (no button held) |
| `0x03` | Drag (button held during motion) |

---

## Config Commands (BEAM → Frontend)

Config commands push editor configuration to the frontend. The TUI silently ignores these (terminal fonts are set by the terminal emulator, not the editor). The macOS GUI applies them immediately.

### `0x50` set_font

Set the font family, size, weight, and ligature preference. Sent once on ready and again when the user changes font config at runtime.

```
opcode:    u8  = 0x50
size:      u16           font size in points
weight:    u8            font weight (see table below)
ligatures: u8            1 = enable programming ligatures, 0 = disable
name_len:  u16           byte length of the font family name
name:      [name_len]u8  UTF-8 encoded font family name
```

Total size: 7 + name_len bytes.

**Weight values:**

| Value | Weight |
|-------|--------|
| 0 | thin |
| 1 | light |
| 2 | regular (default) |
| 3 | medium |
| 4 | semibold |
| 5 | bold |
| 6 | heavy |
| 7 | black |

**Font name resolution (macOS GUI):** The name is a user-friendly display name like "JetBrains Mono" or "Fira Code". The frontend resolves it to an installed font using NSFontManager. PostScript names ("JetBrainsMonoNF-Regular") also work. If the font isn't found, the frontend falls back to the system monospace font and logs a warning.

**Ligature behavior:** When ligatures are enabled and the font supports programming ligatures, the frontend shapes multi-character sequences (like `->`, `!=`, `=>`) using CoreText and renders them as single wide glyphs spanning the appropriate number of cells. When disabled, each character renders individually regardless of font support. Fonts without ligature tables (e.g., Menlo) are unaffected by this flag.

**Grid resize:** Changing the font size changes the cell dimensions, which changes how many cells fit in the window. The frontend sends a `0x02 resize` event back to the BEAM with the new grid dimensions after applying a font change.

---

## Highlight Commands (BEAM → Frontend)

These commands control tree-sitter syntax highlighting. In the current architecture, the frontend process that handles these may be the same as or separate from the renderer (see Architecture Notes below).

### `0x20` set_language

Set the active tree-sitter grammar.

```
opcode:   u8  = 0x20
name_len: u16           byte length of language name
name:     [name_len]u8  language name (e.g., "elixir", "json", "markdown")
```

Total size: 3 + name_len bytes.

**Behavior:** Select the grammar for subsequent parse operations. The frontend should look up the language in its grammar registry. If the language is not found, log a warning and continue (highlighting will be unavailable for this buffer).

### `0x21` parse_buffer

Parse buffer content and return highlight spans.

```
opcode:     u8  = 0x21
version:    u32           monotonically increasing version counter
source_len: u32           byte length of source text
source:     [source_len]u8  UTF-8 encoded buffer content
```

Total size: 9 + source_len bytes.

**Behavior:** Parse the source text with the currently active grammar. Run the highlight query (and injection query, if set) against the parse tree. Send back `highlight_names` (if capture names changed) followed by `highlight_spans` with the version counter. If injection regions are found, also send `injection_ranges`.

The version counter prevents stale results: the BEAM discards spans with a version lower than the most recently requested parse. The frontend should include the version from the request in the `highlight_spans` response.

### `0x22` set_highlight_query

Override the built-in highlight query with custom `.scm` source.

```
opcode:    u8  = 0x22
query_len: u32           byte length of query source
query:     [query_len]u8 tree-sitter query source (.scm format)
```

Total size: 5 + query_len bytes.

**Behavior:** Compile the query for the currently active language and use it for subsequent highlighting. If compilation fails, log a warning and continue with the previous query (or no query). This is used for user-overridden queries from `~/.config/minga/queries/{lang}/highlights.scm`.

### `0x23` load_grammar

Dynamically load a grammar from a shared library.

```
opcode:   u8  = 0x23
name_len: u16           byte length of grammar name
name:     [name_len]u8  grammar name
path_len: u16           byte length of library path
path:     [path_len]u8  filesystem path to .so/.dylib
```

Total size: 5 + name_len + path_len bytes.

**Behavior:** Load the shared library at `path` and look up the symbol `tree_sitter_{name}`. Register the language in the grammar registry. Respond with `grammar_loaded` (opcode `0x32`) indicating success or failure.

### `0x24` set_injection_query

Override the built-in injection query for language embedding (e.g., Markdown fenced code blocks).

```
opcode:    u8  = 0x24
query_len: u32           byte length of query source
query:     [query_len]u8 tree-sitter query source (.scm format)
```

Total size: 5 + query_len bytes.

**Behavior:** Same as `set_highlight_query` but for the injection query. The injection query identifies embedded language regions (e.g., code blocks in Markdown) and their language names.

### `0x25` query_language_at

Ask which language is active at a byte offset (for injection-aware features like comment toggling).

```
opcode:      u8  = 0x25
request_id:  u32           caller-provided correlation ID
byte_offset: u32           byte offset into the last parsed source
```

Total size: 9 bytes.

**Behavior:** Check the injection ranges from the most recent parse. If the byte offset falls within an injection region, return that region's language name. Otherwise, return the outer (root) language name. Respond with `language_at_response` (opcode `0x33`).

---

## Highlight Responses (Frontend → BEAM)

### `0x30` highlight_spans

Syntax highlight byte ranges with capture IDs.

```
opcode:  u8  = 0x30
version: u32           version from the parse_buffer request
count:   u32           number of spans
spans:   [count × 10] array of spans
```

Each span:
```
start_byte: u32           start byte offset in source
end_byte:   u32           end byte offset in source (exclusive)
capture_id: u16           index into the capture names list
```

Total size: 9 + count × 10 bytes.

**Behavior:** Spans are sorted by `(start_byte ASC, layer DESC, pattern_index DESC, end_byte ASC)`. The BEAM uses a first-wins walk: the first span covering a byte position determines its style. Higher-layer spans (from injection languages) take priority over lower-layer spans (from the outer language) at the same position.

### `0x31` highlight_names

Capture name list for interpreting span capture IDs.

```
opcode: u8  = 0x31
count:  u16           number of names
names:  [variable]    array of length-prefixed strings
```

Each name:
```
name_len: u16           byte length of name
name:     [name_len]u8  capture name (e.g., "keyword", "string", "comment")
```

**Behavior:** Sent before or alongside `highlight_spans` whenever the set of capture names changes (typically on first parse or language switch). The BEAM maps capture names to theme colors. The `capture_id` in each span is an index into this list.

### `0x32` grammar_loaded

Response to `load_grammar`.

```
opcode:   u8  = 0x32
success:  u8            1 = success, 0 = failure
name_len: u16           byte length of grammar name
name:     [name_len]u8  grammar name
```

Total size: 4 + name_len bytes.

### `0x33` language_at_response

Response to `query_language_at`.

```
opcode:     u8  = 0x33
request_id: u32           correlation ID from the request
name_len:   u16           byte length of language name (0 if no language set)
name:       [name_len]u8  language name
```

Total size: 7 + name_len bytes.

### `0x34` injection_ranges

Language injection regions found during parsing.

```
opcode: u8  = 0x34
count:  u16           number of ranges
ranges: [variable]    array of injection ranges
```

Each range:
```
start_byte: u32           start byte offset
end_byte:   u32           end byte offset (exclusive)
name_len:   u16           byte length of language name
name:       [name_len]u8  language name for this region
```

**Behavior:** Sent after `highlight_spans` when the parse found embedded language regions (e.g., JSON inside a Markdown fenced code block). The BEAM can use these ranges for injection-aware features like line comment toggling.

### `0x39` textobject_positions

Proactive cache of all `.around` text object positions in the current file, sent after each parse (initial and incremental). The BEAM caches these per-window for `]f`/`[f`-style navigation with zero per-keystroke IPC.

```
opcode:  u8  = 0x39
version: u32           parse version counter
count:   u32           number of entries
entries: [count × 9]   array of position entries
```

Each entry:
```
type_id: u8   text object type (see table below)
row:     u32  0-indexed line number
col:     u32  0-indexed byte column
```

Total size: 9 + count × 9 bytes.

**Type ID values:**

| Value | Type |
|-------|------|
| `0x00` | function |
| `0x01` | class |
| `0x02` | parameter |
| `0x03` | block |
| `0x04` | comment |
| `0x05` | test |

**Behavior:** The Zig parser runs the `textobjects.scm` query against the parse tree and collects the start positions of all `.around` captures (e.g., `@function.around`, `@class.around`). Entries are sorted by (row, col) before sending. The BEAM decodes them into a `%{atom => [{row, col}]}` map and stores them on the active window struct. Navigation commands (`]f`, `[f`, etc.) scan this cached data with no further IPC to Zig.

### `0x3B` request_reparse

Sent by the parser when it receives an `edit_buffer` command with stale edit deltas (byte offsets that don't match the parser's stored source for that buffer). This typically happens after system sleep/wake, when the BEAM's view of the buffer has drifted from the parser's. The parser discards the buffer's tree and source and asks the BEAM to resend the full content via `parse_buffer`.

```
opcode:     u8  = 0x3B
buffer_id:  u32          the buffer that needs a full reparse
```

Total size: 5 bytes.

**Behavior:** The BEAM receives this event, looks up the buffer PID for the given `buffer_id`, and sends a full `set_language` + `parse_buffer` sequence for that buffer. This is the same path used when setting up a buffer for the first time, so custom user queries are replayed correctly.

---

## Log Messages (Frontend → BEAM)

### `0x60` log_message

A log message from the frontend process.

```
opcode:  u8  = 0x60
level:   u8            log level
msg_len: u16           byte length of message
msg:     [msg_len]u8   UTF-8 encoded log text
```

Total size: 4 + msg_len bytes.

**Log level values:**

| Value | Level |
|-------|-------|
| `0x00` | Error |
| `0x01` | Warning |
| `0x02` | Info |
| `0x03` | Debug |

**Behavior:** The BEAM routes these to the `*Messages*` buffer, prefixed with the log level (e.g., `[ZIG/WARN] message text`). Frontends should use this for diagnostic messages that help the user understand what the rendering layer is doing.

---

## Error Handling

**Unknown opcodes:** The receiver should log a warning and skip the command. For batched messages, use `commandSize()` to advance past the unknown command.

**Malformed payloads:** If a payload is too short for its opcode's expected format, the receiver should log a warning and discard the message.

**Version mismatches:** The BEAM discards `highlight_spans` responses where the version is lower than the most recently requested version. This prevents stale async results from overwriting current highlights.

**Frontend crash:** The BEAM's supervisor detects the Port exit and can restart the frontend. Buffer state, undo history, and cursor positions are preserved in the BEAM. The restarted frontend receives a full re-render on its first frame.

---

## Architecture Notes

### Current design

Tree-sitter parsing runs in a dedicated `minga-parser` Zig process, separate from the rendering frontend. The renderer process handles only render commands (`0x10`-`0x1B` plus GUI chrome `0x70`-`0x78`). The parser process handles highlight commands (`0x20`-`0x26`) and sends highlight responses (`0x30`-`0x3B`). Both use the same `{:packet, 4}` framing on their respective stdin/stdout pipes. The BEAM manages both Port processes, routing commands to the appropriate one.

This separation means rendering frontends (Swift/Metal, GTK4, Zig/libvaxis) only need to implement render commands. Tree-sitter parsing is handled by the shared parser process regardless of which frontend is active.

---

## Capability Negotiation

The `ready` event supports an extended format with capability fields. This lets the BEAM adapt rendering strategy based on what the frontend supports.

### Extended Ready Format

```
0x03 ready (extended):
  width:          u16
  height:         u16
  caps_version:   u8    (currently 1)
  caps_len:       u8    (length of remaining fields)
  frontend_type:  u8    (0=tui, 1=native_gui, 2=web)
  color_depth:    u8    (0=mono, 1=256color, 2=rgb)
  unicode_width:  u8    (0=wcwidth, 1=unicode_15)
  image_support:  u8    (0=none, 1=kitty, 2=sixel, 3=native)
  float_support:  u8    (0=emulated, 1=native)
  text_rendering: u8    (0=monospace, 1=proportional)
```

Total size: 13 bytes.

Frontends that send the short 5-byte `ready` format are assumed to have default capabilities: `{tui, rgb, wcwidth, none, emulated, monospace}`.

### `0x05` capabilities_updated

Sent after the initial `ready` event when the frontend detects additional capabilities asynchronously (e.g., a TUI terminal responds to capability queries like DA1 after startup).

```
opcode:         u8  = 0x05
caps_version:   u8    (currently 1)
caps_len:       u8    (length of remaining fields)
frontend_type:  u8
color_depth:    u8
unicode_width:  u8
image_support:  u8
float_support:  u8
text_rendering: u8
```

Total size: 9 bytes.

**Behavior:** The BEAM updates its stored capabilities for this frontend. No re-render is triggered; the updated caps take effect on the next frame.

### Capability Fields

| Field | Values | Description |
|-------|--------|-------------|
| `frontend_type` | 0=tui, 1=native_gui, 2=web | Type of rendering surface |
| `color_depth` | 0=mono, 1=256color, 2=rgb | Color support level |
| `unicode_width` | 0=wcwidth, 1=unicode_15 | Character width calculation method |
| `image_support` | 0=none, 1=kitty, 2=sixel, 3=native | Inline image protocol |
| `float_support` | 0=emulated, 1=native | Floating window support |
| `text_rendering` | 0=monospace, 1=proportional | Font rendering model |

### Implementation Notes

The TUI backend sends `ready` with default capabilities immediately at startup, then sends `capabilities_updated` once libvaxis finishes its async terminal capability detection (triggered by the DA1 response). The GUI backend sends `ready` with full native capabilities upfront since there is no detection delay.

## Layout Regions

Regions express layout structure so frontends can map editor areas to their native abstraction: virtual viewports with clipping (TUI), NSView hierarchy (AppKit), GtkWidget containers (GTK4).

Region ID 0 is the implicit root region (the entire screen). All draw commands before any `set_active_region` target the root.

### `0x14` define_region

Create or update a layout region.

```
opcode:    u8  = 0x14
id:        u16           region identifier (must be > 0)
parent_id: u16           parent region (0 = root)
role:      u8            semantic role (see table below)
row:       u16           top-left row relative to parent
col:       u16           top-left column relative to parent
width:     u16           width in columns
height:    u16           height in rows
z_order:   u8            stacking order (higher = on top)
```

Total size: 15 bytes.

**Region roles:**

| Value | Role | Description |
|-------|------|-------------|
| 0 | editor | Main editor viewport |
| 1 | modeline | Status line |
| 2 | minibuffer | Command/message input area |
| 3 | gutter | Line numbers and signs |
| 4 | popup | Floating completion, which-key, etc. |
| 5 | panel | Side panel (file tree, etc.) |
| 6 | border | Window split borders |

### `0x18` clear_region

Clear all cells within a region to blank.

```
opcode: u8  = 0x18
id:     u16           region to clear
```

Total size: 3 bytes.

### `0x19` destroy_region

Remove a region and clear its area.

```
opcode: u8  = 0x19
id:     u16           region to destroy
```

Total size: 3 bytes.

If the destroyed region was the active region, the frontend resets to the root region.

### `0x1A` set_active_region

Route subsequent `draw_text` commands to a region.

```
opcode: u8  = 0x1A
id:     u16           region to activate (0 = root)
```

Total size: 3 bytes.

**Behavior:** After this command, `draw_text` coordinates are relative to the active region's origin. The frontend offsets and clips draw commands to stay within the region bounds. The active region resets to root on `clear`.

### TUI Implementation

The TUI renderer maintains a hash map of regions. When `set_active_region` is called, the renderer adds the region's row/col offset to every subsequent `draw_text` and clips at the region boundary. `clear_region` blanks only the cells within the region's bounds.

### GUI Implementation

Native GUI frontends should map regions to native view objects: `define_region` creates a view, `destroy_region` removes it, `set_active_region` targets draw commands into the view's coordinate space. The `role` field provides semantic hints for styling and layout behavior.

## Text Measurement

For proportional-font frontends, the BEAM cannot compute display widths on its own. A request/response pair lets the BEAM query the frontend for rendered text width.

### `0x27` measure_text (BEAM → Frontend)

Request the display width of a text segment.

```
opcode:     u8  = 0x27
request_id: u32           unique request identifier
text_len:   u16           byte length of text
text:       [text_len]u8  UTF-8 encoded text to measure
```

Total size: 7 + text_len bytes.

### `0x35` text_width (Frontend → BEAM)

Response with the measured display width.

```
opcode:     u8  = 0x35
request_id: u32           matches the measure_text request
width:      u16           display width in columns (monospace) or pixels (GUI)
```

Total size: 7 bytes.

### Strategy

The BEAM uses a two-tier approach based on the frontend's `text_rendering` capability:

- **Monospace frontends** (TUI, monospace GUI): The BEAM uses its own UAX #11 width tables. `measure_text` is available for spot-checking alignment on startup but is not used per-keystroke.
- **Proportional frontends** (native GUI): The BEAM queries the frontend via `measure_text` for layout-critical computations (cursor placement, column alignment, menu truncation). Results are cached keyed by text content. The cache is flushed when the frontend sends `capabilities_updated` (e.g., after a font size change).

## Incremental Content Sync

### `0x26` edit_buffer

Send compact edit deltas instead of full file content. Enables tree-sitter incremental parsing (sub-millisecond reparse for single-character edits).

```
opcode:     u8  = 0x26
version:    u32           buffer version counter
edit_count: u16           number of edits

per edit:
  start_byte:     u32     byte offset where the edit begins
  old_end_byte:   u32     byte offset where the old text ends
  new_end_byte:   u32     byte offset where the new text ends
  start_row:      u32     row at start_byte
  start_col:      u32     column at start_byte
  old_end_row:    u32     row at old_end_byte
  old_end_col:    u32     column at old_end_byte
  new_end_row:    u32     row at new_end_byte
  new_end_col:    u32     column at new_end_byte
  text_len:       u32     byte length of inserted text
  text:           [text_len]u8  the inserted text (empty for deletions)
```

Total size: 7 + (40 + text_len) per edit.

**Behavior:** The parser applies each edit to its stored copy of the source, calls `ts_tree_edit()` on the existing parse tree, then performs an incremental reparse. Unchanged subtrees are reused, making the reparse cost proportional to the edit size rather than the file size.

**Fallback:** `parse_buffer` (opcode `0x21`) remains available for initial file load, language switches, and error recovery. If incremental parsing fails, the parser falls back to full reparse automatically.

**Edit semantics:** Each edit replaces the byte range `[start_byte, old_end_byte)` with the inserted text. For insertions, `old_end_byte == start_byte`. For deletions, `text_len == 0`. The row/col positions are needed by tree-sitter's `TSInputEdit` for invalidating the correct tree nodes.


---

## GUI Chrome Protocol

Native GUI frontends (SwiftUI, GTK4) receive additional structured data opcodes for chrome elements like tab bars, file trees, status bars, and popups. These opcodes live in the 0x70-0x78 range and are sent only when the frontend reports `frontend_type = native_gui` in its capabilities.

See [GUI_PROTOCOL.md](GUI_PROTOCOL.md) for the complete specification of GUI chrome opcodes, gui_action input events, theme color slots, and the behavioral contract for GUI frontends.

---

## Future: Buffer Fork UI

When [buffer forking](BUFFER-AWARE-AGENTS.md#phase-2-buffer-forking-with-three-way-merge) lands, the protocol may need new opcodes for fork-related UI elements: fork status indicators in the modeline, merge conflict region rendering, or a fork branch picker. These will be additive (new opcodes, no changes to existing ones). Frontend implementors can safely ignore unknown opcodes by reading and discarding the payload based on the length prefix.
