> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monolex.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Synchronous Rendering

> How MonoTerm bypasses xterm.js RenderDebouncer for flicker-free display

# Synchronous Rendering

MonoTerm uses synchronous rendering to eliminate the 1-frame flicker that occurs with xterm.js's default async rendering path.

## The Problem: RenderDebouncer Delay

xterm.js uses a `RenderDebouncer` that schedules all rendering for the next `requestAnimationFrame`. This creates a 1-frame delay between buffer update and screen display.

```
╔═══════════════════════════════════════════════════════════════════════════════╗
║  THE ASYNC RENDERING PROBLEM                                                  ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║  Timeline (16.67ms per frame @ 60fps)                                         ║
║  ─────────────────────────────────────                                        ║
║                                                                               ║
║  Frame N                              Frame N+1                               ║
║  ├──────────────────────────────────┼──────────────────────────────────┤      ║
║  0ms                              16.67ms                           33.33ms   ║
║                                                                               ║
║                                                                               ║
║  T=0ms: GridUpdate arrives                                                    ║
║    │                                                                          ║
║    ▼                                                                          ║
║  ┌─────────────────────────┐                                                  ║
║  │ Buffer Injection        │  ← New data written to buffer                    ║
║  │ (cells written)         │                                                  ║
║  └───────────┬─────────────┘                                                  ║
║              │                                                                ║
║              ▼                                                                ║
║  ┌─────────────────────────┐                                                  ║
║  │ term.refresh()          │                                                  ║
║  │   ↓                     │                                                  ║
║  │ RenderDebouncer         │  ← "I'll render on next RAF"                     ║
║  │   ↓                     │                                                  ║
║  │ requestAnimationFrame() │  ← Schedules and returns immediately             ║
║  └───────────┬─────────────┘                                                  ║
║              │                                                                ║
║              │  ┌─────────────────────────────────────┐                       ║
║              │  │                                     │                       ║
║              │  │  What user sees:                    │                       ║
║              │  │                                     │                       ║
║              │  │  ┌───────────────────────────┐     │                       ║
║              │  │  │ OLD DATA (previous frame) │     │                       ║
║              │  │  │                           │     │  FLICKER!              ║
║              │  │  │ Buffer=NEW, GPU=OLD       │     │  Buffer/screen mismatch║
║              │  │  └───────────────────────────┘     │                       ║
║              │  │                                     │                       ║
║              │  └─────────────────────────────────────┘                       ║
║              │                                                                ║
║              │ ─ ─ ─ ─ ─ ─ ─ ─ ─(16.67ms wait)─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▶  ║
║              │                                        │                       ║
║              │                              Frame N+1 │                       ║
║              │                                        ▼                       ║
║              │                              ┌─────────────────────────┐       ║
║              └─────────────────────────────▶│ RAF callback fires      │       ║
║                                             │   ↓                     │       ║
║                                             │ _renderRows() executes  │       ║
║                                             │   ↓                     │       ║
║                                             │ WebGL Render            │       ║
║                                             └───────────┬─────────────┘       ║
║                                                         │                     ║
║                                                         ▼                     ║
║                                             ┌─────────────────────────┐       ║
║                                             │ Finally shows NEW DATA  │       ║
║                                             └─────────────────────────┘       ║
║                                                                               ║
║  Total delay: ~16.67ms (1 frame)                                              ║
║                                                                               ║
╚═══════════════════════════════════════════════════════════════════════════════╝
```

## The Solution: Direct \_renderRows() Call

MonoTerm bypasses the RenderDebouncer by calling `_renderRows()` directly, achieving immediate rendering.

```
╔═══════════════════════════════════════════════════════════════════════════════╗
║  SYNC RENDERING - IMMEDIATE DISPLAY                                           ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║  Frame N                              Frame N+1                               ║
║  ├──────────────────────────────────┼──────────────────────────────────┤      ║
║                                                                               ║
║  T=0ms: GridUpdate arrives                                                    ║
║    │                                                                          ║
║    ▼                                                                          ║
║  ┌─────────────────────────┐                                                  ║
║  │ Buffer Injection        │  ← New data written to buffer                    ║
║  │ (cells written)         │                                                  ║
║  └───────────┬─────────────┘                                                  ║
║              │                                                                ║
║              ▼                                                                ║
║  ┌─────────────────────────┐                                                  ║
║  │ _renderRows() direct    │  ← RenderDebouncer BYPASSED                      ║
║  │   ↓                     │                                                  ║
║  │ WebGL Render (immediate)│  ← Executes in same JS tick                      ║
║  └───────────┬─────────────┘                                                  ║
║              │                                                                ║
║              ▼                                                                ║
║  ┌─────────────────────────────────────┐                                      ║
║  │                                     │                                      ║
║  │  What user sees:                    │                                      ║
║  │                                     │                                      ║
║  │  ┌───────────────────────────┐     │                                      ║
║  │  │ NEW DATA (immediately!)   │     │  NO FLICKER!                         ║
║  │  │                           │     │                                      ║
║  │  │ Buffer = Screen = Synced  │     │  Perfect match                       ║
║  │  └───────────────────────────┘     │                                      ║
║  │                                     │                                      ║
║  └─────────────────────────────────────┘                                      ║
║                                                                               ║
║  Total delay: ~0ms (immediate)                                                ║
║                                                                               ║
╚═══════════════════════════════════════════════════════════════════════════════╝
```

## Code-Level Difference

### Async Path (xterm.js Public API)

```typescript theme={null}
// Public API - schedules for next frame
term.refresh(0, rows - 1);
    │
    ▼
RenderService.refresh()
    │
    ▼
RenderDebouncer.refresh()
    │
    ▼
this._animationFrame = requestAnimationFrame(() => {
    this._innerRefresh();  // ← Executes on NEXT frame
});
```

### Sync Path (MonoTerm Direct Call)

```
MonoTerm accesses xterm.js's internal render service directly,
bypassing the RenderDebouncer to execute rendering in the same
JS tick as the buffer update. Falls back to async refresh()
if the internal API is unavailable.
```

## Why This Matters for AI CLI

AI tools produce rapid output streams. With async rendering, every update creates a visible delay.

```
╔═══════════════════════════════════════════════════════════════════════════════╗
║  AI CLI OUTPUT SCENARIO                                                       ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║  T=0ms    GridUpdate #1 arrives                                               ║
║  T=2ms    GridUpdate #2 arrives                                               ║
║  T=5ms    GridUpdate #3 arrives                                               ║
║  ...                                                                          ║
║  T=16ms   Frame N+1 starts                                                    ║
║                                                                               ║
║                                                                               ║
║  ASYNC:                              SYNC:                                    ║
║  ──────                              ─────                                    ║
║                                                                               ║
║  #1 buffer update → screen OLD       #1 buffer update → screen NEW            ║
║  #2 buffer update → screen OLD       #2 buffer update → screen NEW            ║
║  #3 buffer update → screen OLD       #3 buffer update → screen NEW            ║
║  ...                                 ...                                      ║
║  Frame N+1: finally shows #3         (already displayed)                      ║
║                                                                               ║
║                                                                               ║
║  User Experience:                    User Experience:                         ║
║  ────────────────                    ────────────────                         ║
║  Screen "lags behind"                Real-time response                       ║
║  Visible flicker on fast output      Always smooth                            ║
║                                                                               ║
╚═══════════════════════════════════════════════════════════════════════════════╝
```

## Implementation Sites

MonoTerm applies synchronous rendering at 15 critical points across 5 files:

| File                            | Sites | Purpose                 |
| ------------------------------- | ----- | ----------------------- |
| `atomic-cell-injector.ts`       | 6     | Grid injection render   |
| `terminal-instance.ts`          | 2     | Terminal initialization |
| `terminal-scrollbar.ts`         | 3     | Scroll position updates |
| `terminal-renderer-selector.ts` | 1     | Renderer switching      |
| `fonts-terminal.ts`             | 3     | Font change refresh     |

## Why Debouncer Exists (And Why We Bypass It)

The RenderDebouncer serves a purpose in traditional terminals:

```
╔═══════════════════════════════════════════════════════════════════════════════╗
║  DEBOUNCER PURPOSE vs MONOTERM REALITY                                        ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║  Why xterm.js uses RenderDebouncer:                                           ║
║  ──────────────────────────────────                                           ║
║                                                                               ║
║  1. Coalesce rapid updates into single render                                 ║
║  2. Prevent excessive GPU calls                                               ║
║  3. Sync with browser's natural 60fps refresh                                 ║
║                                                                               ║
║                                                                               ║
║  Why MonoTerm can bypass it:                                                  ║
║  ───────────────────────────                                                  ║
║                                                                               ║
║  1. Rust AtomicState already calculates diff (Full/Partial/None/Skip)         ║
║     → Redundant to debounce what's already optimized                          ║
║                                                                               ║
║  2. ACK flow control limits update rate                                       ║
║     → Backend won't flood frontend                                            ║
║                                                                               ║
║  3. BSU/ESU protocol batches atomic frames                                    ║
║     → Updates arrive pre-batched                                              ║
║                                                                               ║
║  4. Frontend RAF already caps at 60fps                                        ║
║     → Natural rate limiting exists                                            ║
║                                                                               ║
║                                                                               ║
║  Result: Debouncer is REDUNDANT in Atomic Architecture                        ║
║          Bypassing it gives immediate response with no downside               ║
║                                                                               ║
╚═══════════════════════════════════════════════════════════════════════════════╝
```

## Comparison Summary

| Aspect   | Async (refresh)       | Sync (\_renderRows) |
| -------- | --------------------- | ------------------- |
| Delay    | 1 frame (\~16.67ms)   | 0ms (immediate)     |
| Flicker  | Possible              | Eliminated          |
| API      | Public                | Private (internal)  |
| Use Case | Traditional terminals | AI-native terminals |

## Related Documentation

<CardGroup cols={2}>
  <Card title="Diff Rendering" icon="code-compare" href="/terminal/rendering/diff-rendering">
    How AtomicState calculates minimal updates
  </Card>

  <Card title="ACK Flow Control" icon="handshake" href="/architecture/data-flow-and-sync/ack-flow-control">
    Backend rate limiting that enables sync render
  </Card>

  <Card title="BSU/ESU Protocol" icon="brackets-curly" href="/terminal/atomic-state/bsu-esu-protocol">
    Atomic frame batching from PTY
  </Card>

  <Card title="Atomic Loop" icon="rotate" href="/terminal/atomic-state/atomic-loop">
    The complete rendering cycle
  </Card>
</CardGroup>
