Buffer Model
MonoTerm uses an optimized memory model to ensure scroll consistency while minimizing memory usage.The Problem
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ THE PROBLEM: Scroll Desync │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ xterm.js calculates scroll based on buffer size: │
│ │
│ scrollAreaHeight = buffer.lines.length x rowHeight │
│ maxScrollTop = scrollAreaHeight - viewportHeight │
│ │
│ If buffer doesn't match scrollArea: │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ buffer.lines = X │ != │ scrollArea = Y │ │
│ └──────────────────┘ └──────────────────┘ │
│ │ │ │
│ └───────────┬───────────┘ │
│ ▼ │
│ SCROLL DESYNC! │
│ - Scroll jumps │
│ - Thumb size wrong │
│ - Position calculation broken │
│ │
└───────────────────────────────────────────────────────────────────────┘
The Solution: Optimized Memory Layout
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ BUFFER STRUCTURE │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ Variables: │
│ H = history (scrollback lines) │
│ V = viewport (visible rows) │
│ │
│ │
│ Index: 0 H-1 H H+V-1 H+V H+2V-1 │
│ │ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ ▼ │
│ ┌───────┬─────────┬─────┬──────┬──────┬──────┬───────┐ │
│ │ ##################### │/////////////│@@@@@@@@@@@@@ │ │
│ │ HISTORY (H) │ VIEWPORT │ PADDING │ │
│ │ scrollback │ (V) │ (V) │ │
│ │ actual data │ actual data │ empty lines │ │
│ └───────────────────────┴─────────────┴──────────────┘ │
│ │
│ Legend: │
│ #### = History lines (scrollback content) │
│ //// = Viewport lines (visible screen content) │
│ @@@@ = Padding lines (empty, for scroll consistency) │
│ │
│ Actual content: H + V lines (from Rust) │
│ Total size: H + 2V lines (with padding) │
│ Padding: V lines (constant, regardless of H) │
│ │
└───────────────────────────────────────────────────────────────────────┘
Why This Layout Works
Safety Guarantee
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ ARRAY BOUNDS SAFETY │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ Maximum access pattern in xterm.js: │
│ │
│ buffer.lines[ydisp + y] │
│ where: ydisp = H (at bottom) │
│ y = 0..V-1 (viewport rows) │
│ │
│ Maximum index accessed: │
│ max_index = H + (V - 1) │
│ = H + V - 1 │
│ │
│ Buffer length: │
│ buffer.lines.length = H + 2V │
│ │
│ Safety check: │
│ H + V - 1 < H + 2V │
│ V - 1 < 2V │
│ -1 < V ALWAYS TRUE (for V > 0) │
│ │
│ Result: Buffer access is always within bounds │
│ │
└───────────────────────────────────────────────────────────────────────┘
Memory Efficiency
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ MEMORY COMPARISON │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ With H=1000 (history), V=40 (viewport): │
│ │
│ │
│ Alternative Model (wasteful): │
│ ────────────────────────────── │
│ buffer = 2H + V = 2040 lines │
│ padding = H = 1000 lines (grows with history!) │
│ │
│ H=0 -> padding = 0 │
│ H=100 -> padding = 100 │
│ H=10000 -> padding = 10000 WASTEFUL! │
│ │
│ │
│ MonoTerm Model (efficient): │
│ ─────────────────────────── │
│ buffer = H + 2V = 1080 lines │
│ padding = V = 40 lines (constant!) │
│ │
│ V=40 -> padding = 40 ALWAYS ~40 lines │
│ Regardless of history size! │
│ │
│ │
│ Memory savings with 10,000 line history: │
│ │
│ Alternative: 20,040 lines │
│ MonoTerm: 10,080 lines │
│ Saved: 9,960 lines (99%) │
│ │
└───────────────────────────────────────────────────────────────────────┘
Visual Memory Layout
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ MEMORY LAYOUT VISUALIZATION │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ Example: H=100 (history), V=40 (viewport) │
│ │
│ │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │
│ │ 0 │ 1 │ 2 │ ... │ 99 │ 100 │ 101 │ ... │ 139 │ 140 │ │
│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │
│ │<─────────── H=100 ──────────→│<───── V=40 ─────→│<── V=40 ──→│ │
│ │
│ │
│ Region │ Indices │ Content │ Source │
│ ───────────┼────────────┼──────────────┼───────────── │
│ History │ 0..99 │ Scrollback │ Rust data │
│ Viewport │ 100..139 │ Screen │ Rust data │
│ Padding │ 140..179 │ Empty │ (no data) │
│ │
│ │
│ Total length: 180 = H(100) + 2V(80) │
│ Actual data: 140 = H(100) + V(40) │
│ Padding: 40 = V │
│ │
└───────────────────────────────────────────────────────────────────────┘
Edge Cases
No History (Fresh Terminal)
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ EDGE CASE: Empty History │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ When: Terminal just started, no scrollback yet │
│ │
│ Values: │
│ H = 0, V = 40 │
│ │
│ Layout: │
│ ┌─────────────────────────────────┬───────────────────────────────┐│
│ │/////////////////////////////////│@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@││
│ │ VIEWPORT (V=40) │ PADDING (V=40) ││
│ └─────────────────────────────────┴───────────────────────────────┘│
│ Index: 0 39 40 79│
│ │
│ Buffer size: 0 + 80 = 80 lines │
│ Safety: max_index = 0 + 39 = 39 < 80 OK │
│ │
└───────────────────────────────────────────────────────────────────────┘
Large History
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ EDGE CASE: Large Scrollback │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ When: Long-running terminal, lots of output │
│ │
│ Values: │
│ H = 100,000 (max scrollback) │
│ V = 40 │
│ │
│ │
│ Alternative (wasteful): │
│ buffer.length = 200,040 lines │
│ padding = 100,000 lines (same as history!) │
│ │
│ │
│ MonoTerm (efficient): │
│ buffer.length = 100,080 lines │
│ padding = 40 lines (same as viewport) │
│ │
│ │
│ Memory savings: 99,960 empty lines avoided! │
│ │
│ Safety: max_index = 100,039 < 100,080 OK │
│ │
└───────────────────────────────────────────────────────────────────────┘
Scrollbar Integration
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ SCROLLBAR SIZE CALCULATION │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ From GridUpdate: │
│ totalLines = H + 2V │
│ viewportRows = V │
│ │
│ Thumb SIZE (proportion): │
│ thumbHeight = (V / (H + 2V)) x trackHeight │
│ │
│ Thumb POSITION: │
│ scrollableRange = H + V │
│ scrollFraction = ydisp / scrollableRange │
│ thumbTop = scrollFraction x (trackHeight - thumbHeight) │
│ │
│ │
│ Track representation: │
│ │
│ ┌───┐ │
│ │...│ <- History region (H) │
│ │...│ │
│ │...│ │
│ │###│ <- Thumb (V / (H+2V) of track) │
│ │...│ <- Padding region (V) │
│ └───┘ │
│ │
└───────────────────────────────────────────────────────────────────────┘
Scroll Position Mapping
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ SCROLL POSITION MAPPING │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ ydisp (internal) <--> scrollTop (DOM pixel) │
│ │
│ Conversion: │
│ scrollTop = ydisp x rowHeight │
│ ydisp = Math.floor(scrollTop / rowHeight) │
│ │
│ Range: │
│ ydisp: 0 (top of history) to H (bottom, current screen) │
│ scrollTop: 0 to (H + V) x rowHeight │
│ │
│ Visual: │
│ │
│ ydisp=0 ydisp=H/2 ydisp=H │
│ scrollTop=0 scrollTop=M scrollTop=max │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │#######│ │.......│ │.......│ <- viewport window │
│ │#######│ │#######│ │.......│ │
│ │.......│ │#######│ │#######│ │
│ │.......│ │.......│ │#######│ │
│ └───────┘ └───────┘ └───────┘ │
│ top of middle of bottom of │
│ history history history (current) │
│ │
└───────────────────────────────────────────────────────────────────────┘
Summary
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ BUFFER MODEL SUMMARY │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ [OK] WHAT IT SOLVES: │
│ - xterm.js scroll/buffer desync │
│ - Unpredictable buffer growth │
│ - Scrollbar calculation errors │
│ │
│ [OK] HOW IT WORKS: │
│ - Buffer = Content (H+V) + Padding (V) │
│ - Padding is always viewport-sized │
│ - scrollArea height matches buffer length │
│ │
│ [OK] WHY IT'S SAFE: │
│ - Max access: H + V - 1 │
│ - Buffer size: H + 2V │
│ - H + V - 1 < H + 2V (always true for V > 0) │
│ │
│ [OK] WHY IT'S EFFICIENT: │
│ - Padding = V (constant ~40 lines) │
│ - Not H (could be 100,000 lines!) │
│ - Memory grows linearly with content, not doubled │
│ │
└───────────────────────────────────────────────────────────────────────┘
Memory Efficiency
Constant padding of ~40 lines regardless of scrollback size. With 100K history: saves 99,960 empty lines compared to alternative models.