# Herdr Pane Scrollback Buffer Management: Architecture and Implementation

> Explore Herdr pane scrollback buffer management. Learn how Herdr implements a bounded byte buffer with fixed memory limits and persistence across sessions.

- Repository: [Can Celik/herdr](https://github.com/ogulcancelik/herdr)
- Tags: architecture
- Published: 2026-05-31

---

**Herdr treats pane scrollback as a pure, bounded byte buffer managed entirely within the application's state layer, enforcing fixed memory limits through the `TerminalRuntime` struct while supporting full persistence across sessions.**

In the `ogulcancelik/herdr` terminal workspace application, scrollback buffer management follows a strict separation between state and runtime concerns. This design ensures predictable memory usage and seamless integration with the UI rendering pipeline, regardless of session duration or output volume.

## Where Scrollback Lives in the Codebase

The scrollback implementation spans six core components that handle everything from memory allocation to UI visualization:

| Component | Role | Key Source |
|---|---|---|
| **`TerminalRuntime`** | Owns the runtime representation of a pane, including the bounded scrollback buffer and its size limit. | [[`src/terminal/runtime.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/terminal/runtime.rs)](https://github.com/ogulcancelik/herdr/blob/master/src/terminal/runtime.rs) |
| **`PaneRuntime`** | Wraps `TerminalRuntime` to provide the high-level API used by the UI and server layers. | [[`src/pane/terminal.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/pane/terminal.rs)](https://github.com/ogulcancelik/herdr/blob/master/src/pane/terminal.rs) |
| **`Workspace` / `Tab`** | Propagate the user-configurable `scrollback_limit_bytes` down to each pane during creation. | [[`src/workspace.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/workspace.rs)](https://github.com/ogulcancelik/herdr/blob/master/src/workspace.rs) & [[`src/workspace/tab.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/workspace/tab.rs)](https://github.com/ogulcancelik/herdr/blob/master/src/workspace/tab.rs) |
| **UI Scrollbars** | Render visual scrollbars only when the pane's scrollback buffer contains data. | [[`src/ui/panes.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/ui/panes.rs)](https://github.com/ogulcancelik/herdr/blob/master/src/ui/panes.rs) |
| **Persistence Layer** | Serialises and restores scrollback bytes with workspace state. | [[`src/persist/snapshot.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/persist/snapshot.rs)](https://github.com/ogulcancelik/herdr/blob/master/src/persist/snapshot.rs) & [[`src/persist/restore.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/persist/restore.rs)](https://github.com/ogulcancelik/herdr/blob/master/src/persist/restore.rs) |

## Buffer Allocation and Memory Bounds

When a pane is instantiated, Herdr stores the `scrollback_limit_bytes` value (defaulting to **1024 KB**) inside the `TerminalRuntime` struct:

```rust
pub struct TerminalRuntime {
    // …
    scrollback_limit_bytes: usize,
    // …
}

```

The runtime creates an internal `ScrollbackBuffer` that grows dynamically until it reaches the byte limit. Once the limit is exceeded, the oldest data is automatically discarded, guaranteeing a fixed memory footprint regardless of how long a session runs or how much output is generated.

## Writing to the Scrollback Buffer

All terminal output flows through the runtime API, ensuring the scrollback remains synchronized with the visible screen. Data passes from `PaneRuntime::write` to `TerminalRuntime::write`, which appends bytes to both the primary screen buffer and the scrollback buffer simultaneously.

The system also handles **clear-scrollback escape sequences** (such as `ESC [3J`). In [[`src/pane/terminal.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/pane/terminal.rs)](https://github.com/ogulcancelik/herdr/blob/master/src/pane/terminal.rs), the runtime detects these sequences and discards existing scrollback contents:

```rust
if (!alternate_screen && contains_scrollback_clear_sequence(bytes)) {
    maybe_filter_primary_screen_scrollback_clear(...)
}

```

This keeps the buffer in sync with host terminal expectations, maintaining compatibility with agents like *droid* that rely on explicit scrollback clearing.

## Reading and Rendering Scrollback

The UI layer never mutates the buffer directly. Instead, it queries the runtime for rendered views. The `PaneRuntime::render` method returns a `RenderResult` containing a `scrollback_view` field, which provides the exact byte slice needed for the current viewport.

In [[`src/ui/panes.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/ui/panes.rs)](https://github.com/ogulcancelik/herdr/blob/master/src/ui/panes.rs), the UI determines scrollbar visibility based on buffer state:

```rust
let show_scrollbar = terminal.has_scrollback();

```

The `has_scrollback()` method inspects the underlying buffer size, ensuring scrollbars appear only when historical data exists.

## User Interaction and Scroll Limits

Users interact with scrollback through multiple input methods, all handled in the UI layer:

- **Mouse wheel** scrolling
- **Arrow key** navigation  
- **Scrollbar gutter** dragging

When these events occur, the code in [`src/ui/panes.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/ui/panes.rs) updates `pane.scroll_offset` and triggers a re-render. The runtime enforces bounds checking, preventing scroll offsets from exceeding available scrollback data.

## Persistence Across Sessions

Herdr's scrollback buffer survives application restarts through the persistence layer. When a workspace is saved via [`persist/snapshot.rs`](https://github.com/ogulcancelik/herdr/blob/main/persist/snapshot.rs), the scrollback bytes are serialised alongside pane state. During restoration via [`persist/restore.rs`](https://github.com/ogulcancelik/herdr/blob/main/persist/restore.rs), the `TerminalRuntime` receives the original `scrollback_limit_bytes` value, ensuring consistent buffer constraints across sessions.

## Summary

- **State-only architecture**: Scrollback lives as pure data inside `TerminalRuntime`, separate from UI rendering code.
- **Bounded memory**: The `scrollback_limit_bytes` parameter (default 1024 KB) enforces fixed memory usage through automatic eviction of old data.
- **Runtime API enforcement**: All mutations pass through `PaneRuntime::write` and `TerminalRuntime::write`, with special handling for clear-scrollback escape sequences.
- **Conditional UI rendering**: Scrollbars appear only when `has_scrollback()` returns true, based on actual buffer contents.
- **Full session persistence**: Scrollback data serialises with workspace snapshots and restores with identical memory constraints.

## Frequently Asked Questions

### How does Herdr prevent scrollback from consuming unlimited memory?

Herdr enforces a strict byte limit through the `scrollback_limit_bytes` field in `TerminalRuntime`. When the buffer exceeds this limit (default 1024 KB), the runtime automatically drops the oldest bytes to maintain constant memory usage, regardless of session duration or output volume.

### Can scrollback data survive application restarts?

Yes. Herdr serialises the scrollback buffer during workspace snapshots in [`src/persist/snapshot.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/persist/snapshot.rs) and restores it via [`src/persist/restore.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/persist/restore.rs). When restored, the pane receives the same `scrollback_limit_bytes` configuration, maintaining consistent behavior across sessions.

### How does Herdr handle the "clear scrollback" command from terminal applications?

The runtime detects clear-scrollback escape sequences (such as `ESC [3J`) in [`src/pane/terminal.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/pane/terminal.rs) using `contains_scrollback_clear_sequence()`. When detected, it immediately discards existing scrollback contents through `maybe_filter_primary_screen_scrollback_clear()`, ensuring compatibility with applications that expect scrollback clearing to work as in traditional terminals.

### What determines when the scrollbar appears in the UI?

The UI layer in [`src/ui/panes.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/ui/panes.rs) calls `terminal.has_scrollback()` to check for non-empty buffer contents. The scrollbar renders only when this method returns true, ensuring visual clutter is minimized for panes without historical output.