Skip to main content

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.

Epoch Synchronization

EPOCH is MonoTerm’s solution for preventing resize race conditions between frontend and backend.

The Resize Problem

When a terminal is resized, multiple components must be updated synchronously.
╔════════════════════════════════════════════════════════════════════════╗
║                                                                        ║
║  TERMINAL RESIZE INVOLVES MULTIPLE COMPONENTS                          ║
║                                                                        ║
║                                                                        ║
║    When user resizes the window:                                       ║
║                                                                        ║
║                                                                        ║
║    ┌───────────────┐  ┌───────────────┐  ┌───────────────┐             ║
║    │               │  │               │  │               │             ║
║    │   xterm.js    │  │    Rust       │  │     PTY       │             ║
║    │   (Frontend)  │  │   (Backend)   │  │   (Daemon)    │             ║
║    │               │  │               │  │               │             ║
║    │  120 x 40     │  │   120 x 40    │  │   120 x 40    │             ║
║    │               │  │               │  │               │             ║
║    └───────────────┘  └───────────────┘  └───────────────┘             ║
║                                                                        ║
║                                                                        ║
║    All THREE must have the SAME dimensions.                            ║
║    If they disagree, rendering will be corrupted.                      ║
║                                                                        ║
╚════════════════════════════════════════════════════════════════════════╝

The Race Condition

Resize notifications travel at different speeds through the system.
╔════════════════════════════════════════════════════════════════════════╗
║                                                                        ║
║  THE RACE CONDITION                                                    ║
║                                                                        ║
║                                                                        ║
║    Timeline of a resize from 80x24 to 120x40:                          ║
║                                                                        ║
║                                                                        ║
║    Time   xterm.js         Rust Backend        PTY/Shell               ║
║    ────   ─────────────    ─────────────       ──────────              ║
║                                                                        ║
║    T0     80 x 24          80 x 24             80 x 24                 ║
║           │                 │                   │                      ║
║           │ User resizes    │                   │                      ║
║           v window          │                   │                      ║
║    T1     120 x 40          │                   │                      ║
║           │                 │                   │                      ║
║           │ Send resize     │                   │                      ║
║           │ request ──────▶ │                   │                      ║
║           │                 v                   │                      ║
║    T2     120 x 40         80 x 24              │                      ║
║           │                 │ Processing...     │                      ║
║           │                 │                   │                      ║
║           │                 │                   │                      ║
║    T3     │                 │ GridUpdate        │                      ║
║           │                 │ (80x24) sent!     │                      ║
║           │ ◀────────────── │                   │                      ║
║           │                 │                   │                      ║
║           │ !!! SIZE        │                   │                      ║
║           │   MISMATCH!     │                   │                      ║
║           │   120x40 !=     │                   │                      ║
║           │   80x24         v                   │                      ║
║    T4     │                120 x 40             │                      ║
║           │                 │ Forward to PTY    │                      ║
║           │                 │ ───────────────▶  │                      ║
║           │                 │                   v                      ║
║    T5     │                 │                  120 x 40                ║
║           │                 │                   │                      ║
║           │                 │                   │                      ║
║    T6     │                 │ GridUpdate        │                      ║
║           │ ◀────────────── │ (120x40) sent     │                      ║
║           │                 │                   │                      ║
║           │ Sizes match OK  │                   │                      ║
║           v                 v                   v                      ║
║                                                                        ║
║                                                                        ║
║    The Problem:                                                        ║
║                                                                        ║
║    At T3, xterm.js receives a GridUpdate with dimensions               ║
║    80x24, but xterm.js is already 120x40.                              ║
║                                                                        ║
║    If we inject this update, text will wrap incorrectly,               ║
║    cursor will be at wrong position, display will be corrupt.          ║
║                                                                        ║
╚════════════════════════════════════════════════════════════════════════╝

EPOCH: The Solution

EPOCH is a version number that increments on each resize.
╔═════════════════════════════════════════════════════════════════════════╗
║                                                                         ║
║  EPOCH VERSIONING                                                       ║
║                                                                         ║
║                                                                         ║
║    Concept:                                                             ║
║                                                                         ║
║    - Each resize increments a counter called "EPOCH"                    ║
║    - EPOCH is included in every GridUpdate                              ║
║    - Frontend rejects updates with old EPOCH                            ║
║                                                                         ║
║                                                                         ║
║    ┌─────────────────────────────────────────────────────────────┐      ║
║    │                                                             │      ║
║    │   Frontend                       Backend                    │      ║
║    │                                                             │      ║
║    │   currentEpoch: 5                                           │      ║
║    │                                                             │      ║
║    │        │                              │                     │      ║
║    │        │ User resizes window          │                     │      ║
║    │        │                              │                     │      ║
║    │        │ currentEpoch++ (now 6)       │                     │      ║
║    │        │                              │                     │      ║
║    │        │ resize_session(epoch: 6)     │                     │      ║
║    │        │ ────────────────────────────▶ │                    │      ║
║    │        │                              │                     │      ║
║    │        │                              │ Store epoch = 6     │      ║
║    │        │                              │                     │      ║
║    │        │    GridUpdate (epoch: 5)     │                     │      ║
║    │        │ ◀─────────────────────────── │                     │      ║
║    │        │                              │                     │      ║
║    │        │ 5 < 6 ──▶ DISCARD            │                     │      ║
║    │        │ (stale update)               │                     │      ║
║    │        │                              │                     │      ║
║    │        │    GridUpdate (epoch: 6)     │                     │      ║
║    │        │ ◀─────────────────────────── │                     │      ║
║    │        │                              │                     │      ║
║    │        │ 6 == 6 ──▶ ACCEPT            │                     │      ║
║    │        │ (current update)             │                     │      ║
║    │        │                              │                     │      ║
║    │        v                              v                     │      ║
║    │                                                             │      ║
║    └─────────────────────────────────────────────────────────────┘      ║
║                                                                         ║
║                                                                         ║
║    Rule:                                                                ║
║                                                                         ║
║    if (update.epoch < currentEpoch) {                                   ║
║        // This update was generated before the resize                   ║
║        // DISCARD IT - dimensions are wrong                             ║
║    }                                                                    ║
║                                                                         ║
╚═════════════════════════════════════════════════════════════════════════╝

Frontend-Initiated Pattern

The epoch is managed by the frontend, not the backend.
╔════════════════════════════════════════════════════════════════════════╗
║                                                                        ║
║  FRONTEND-INITIATED EPOCH PATTERN                                      ║
║                                                                        ║
║                                                                        ║
║    Why Frontend Controls Epoch:                                        ║
║                                                                        ║
║    The frontend knows FIRST when a resize happens (xterm.js event).    ║
║    It must increment epoch BEFORE sending resize to backend.           ║
║    This ensures any in-flight GridUpdates are marked as stale.         ║
║                                                                        ║
║                                                                        ║
║    ┌──────────────────────────────────────────────────────────┐        ║
║    │                                                          │        ║
║    │  Frontend (xterm.js)           Backend (Rust)            │        ║
║    │                                                          │        ║
║    │  1. Window resize event        │                         │        ║
║    │     detected                   │                         │        ║
║    │                                │                         │        ║
║    │  2. prepareResize()            │                         │        ║
║    │     currentEpoch++             │                         │        ║
║    │     (now epoch=6)              │                         │        ║
║    │                                │                         │        ║
║    │  3. requestResize(cols, rows,  │                         │        ║
║    │     epoch=6)                   │                         │        ║
║    │     ───────────────────────────▶ resize(c, r, epoch=6)   │        ║
║    │                                │ epoch = 6               │        ║
║    │                                │                         │        ║
║    │  4. Any GridUpdate with        │                         │        ║
║    │     epoch < 6 is REJECTED      │                         │        ║
║    │                                │                         │        ║
║    └──────────────────────────────────────────────────────────┘        ║
║                                                                        ║
╚════════════════════════════════════════════════════════════════════════╝

Complete EPOCH Flow

Step-by-step walkthrough of a resize with EPOCH.
╔════════════════════════════════════════════════════════════════════════╗
║                                                                        ║
║  EPOCH FLOW: RESIZE FROM 80x24 TO 120x40                               ║
║                                                                        ║
║                                                                        ║
║   Frontend                 Backend                   PTY               ║
║   (epoch=5)               (epoch=5)               (80x24)              ║
║       │                       │                       │                ║
║       │                       │                       │                ║
║  ─────┼───────────────────────┼───────────────────────┼─────           ║
║   1   │ Window resize event   │                       │                ║
║       │ xterm now 120x40      │                       │                ║
║       │                       │                       │                ║
║  ─────┼───────────────────────┼───────────────────────┼─────           ║
║   2   │ prepareResize()       │                       │                ║
║       │ epoch++ (now 6)       │                       │                ║
║       │                       │                       │                ║
║       │ requestResize({       │                       │                ║
║       │   cols: 120,          │                       │                ║
║       │   rows: 40,           │                       │                ║
║       │   epoch: 6            │                       │                ║
║       │ }) ───────────────────▶                       │                ║
║       │                       │                       │                ║
║  ─────┼───────────────────────┼───────────────────────┼─────           ║
║   3   │                       │ Meanwhile, shell      │                ║
║       │                       │ outputs data...       │                ║
║       │                       │                       │                ║
║       │                       │ GridUpdate (epoch=5)  │                ║
║       │◀──────────────────────│                       │                ║
║       │                       │                       │                ║
║       │ DISCARD (5 < 6)       │                       │                ║
║       │                       │                       │                ║
║  ─────┼───────────────────────┼───────────────────────┼─────           ║
║   4   │                       │ Process resize        │                ║
║       │                       │ epoch = 6             │                ║
║       │                       │                       │                ║
║       │                       │ resize_pty(120,40) ───┼────▶           ║
║       │                       │                       │                ║
║  ─────┼───────────────────────┼───────────────────────┼─────           ║
║   5   │                       │                       │ 120x40         ║
║       │                       │                       │                ║
║       │                       │ Shell re-renders...   │                ║
║       │                       │◀──────────────────────│                ║
║       │                       │                       │                ║
║       │                       │ GridUpdate (epoch=6)  │                ║
║       │◀──────────────────────│                       │                ║
║       │                       │                       │                ║
║       │ ACCEPT (6 >= 6)       │                       │                ║
║       │ Inject to display     │                       │                ║
║       │                       │                       │                ║
║  ─────┼───────────────────────┼───────────────────────┼─────           ║
║   6   │ Display correct       │                       │                ║
║       │ 120x40 content        │                       │                ║
║       │                       │                       │                ║
║       v                       v                       v                ║
║                                                                        ║
╚════════════════════════════════════════════════════════════════════════╝

Double Verification

Even with EPOCH, we verify actual dimensions match.
╔════════════════════════════════════════════════════════════════════════╗
║                                                                        ║
║  DOUBLE VERIFICATION                                                   ║
║                                                                        ║
║                                                                        ║
║    EPOCH alone is not enough. We also check dimensions:                ║
║                                                                        ║
║                                                                        ║
║    Validation Steps:                                                   ║
║                                                                        ║
║    ┌─────────────────────────────────────────────┐                     ║
║    │                                             │                     ║
║    │  Step 1: EPOCH Check                        │                     ║
║    │                                             │                     ║
║    │  update.epoch < currentEpoch ?              │                     ║
║    │                                             │                     ║
║    │    YES ──▶ DISCARD (stale)                  │                     ║
║    │    NO  ──▶ Continue to Step 2               │                     ║
║    │                                             │                     ║
║    └─────────────────────────────────────────────┘                     ║
║                          │                                             ║
║                          v                                             ║
║    ┌─────────────────────────────────────────────┐                     ║
║    │                                             │                     ║
║    │  Step 2: Dimension Check                    │                     ║
║    │                                             │                     ║
║    │  update.cols == xterm.cols AND              │                     ║
║    │  update.rows == xterm.rows ?                │                     ║
║    │                                             │                     ║
║    │    NO  ──▶ Request backend resize           │                     ║
║    │          Skip injection                     │                     ║
║    │    YES ──▶ Continue to Step 3               │                     ║
║    │                                             │                     ║
║    └─────────────────────────────────────────────┘                     ║
║                          │                                             ║
║                          v                                             ║
║    ┌─────────────────────────────────────────────┐                     ║
║    │                                             │                     ║
║    │  Step 3: Inject to Display                  │                     ║
║    │                                             │                     ║
║    │  Safe to inject - all checks passed         │                     ║
║    │                                             │                     ║
║    └─────────────────────────────────────────────┘                     ║
║                                                                        ║
║                                                                        ║
║    Why Both Checks?                                                    ║
║                                                                        ║
║    EPOCH: Catches updates generated before resize                      ║
║    Dimension: Catches unexpected size differences                      ║
║                                                                        ║
║    Belt AND suspenders. Both are needed for safety.                    ║
║                                                                        ║
╚════════════════════════════════════════════════════════════════════════╝

Why Not Just Use Dimensions?

Why do we need EPOCH when we could just compare dimensions?
╔════════════════════════════════════════════════════════════════════════╗
║                                                                        ║
║  WHY EPOCH IS NECESSARY                                                ║
║                                                                        ║
║                                                                        ║
║    Scenario: Rapid Resize                                              ║
║                                                                        ║
║    User quickly resizes: 80x24 ──▶ 120x40 ──▶ 80x24                    ║
║                                                                        ║
║                                                                        ║
║    Time    Action                        Dimensions                    ║
║    ────    ──────                        ──────────                    ║
║    T1      Start                         80 x 24                       ║
║    T2      Resize to 120x40              120 x 40                      ║
║    T3      GridUpdate (80x24) in flight  ...                           ║
║    T4      Resize back to 80x24          80 x 24                       ║
║    T5      GridUpdate (80x24) arrives    ...                           ║
║                                                                        ║
║                                                                        ║
║    Without EPOCH:                                                      ║
║                                                                        ║
║    At T5, dimensions match (80x24 == 80x24).                           ║
║    But this update was generated at T1, before T2's resize!            ║
║    The content is STALE - cursor position is wrong.                    ║
║                                                                        ║
║    Dimension check alone: PASS (80 == 80, 24 == 24)                    ║
║    But update is stale:   WRONG CONTENT!                               ║
║                                                                        ║
║                                                                        ║
║    With EPOCH:                                                         ║
║                                                                        ║
║    T1: epoch = 5, generate update (epoch=5, 80x24)                     ║
║    T2: epoch++ = 6, resize to 120x40                                   ║
║    T4: epoch++ = 7, resize to 80x24                                    ║
║    T5: update (epoch=5) arrives, currentEpoch=7                        ║
║                                                                        ║
║    EPOCH check: 5 < 7 ──▶ DISCARD                                      ║
║                                                                        ║
║    We correctly reject the stale update!                               ║
║                                                                        ║
╚════════════════════════════════════════════════════════════════════════╝

Summary

╔════════════════════════════════════════════════════════════════════════╗
║                                                                        ║
║  EPOCH SYNCHRONIZATION IN ONE DIAGRAM                                  ║
║                                                                        ║
║                                                                        ║
║                                                                        ║
║       FRONTEND                              BACKEND                    ║
║    ┌────────────────┐                  ┌────────────────┐              ║
║    │                │                  │                │              ║
║    │  currentEpoch  │                  │   Grid State   │              ║
║    │     ┌───┐      │                  │   .epoch       │              ║
║    │     │ 5 │      │                  │     ┌───┐      │              ║
║    │     └───┘      │                  │     │ 5 │      │              ║
║    │        │       │                  │     └───┘      │              ║
║    │        │       │                  │        │       │              ║
║    │ On resize:     │                  │        │       │              ║
║    │ prepareResize()│                  │        │       │              ║
║    │ epoch++ (now 6)│ requestResize()  │        │       │              ║
║    │                │─────────────────▶│ epoch = 6      │              ║
║    │                │                  │                │              ║
║    │                │  GridUpdate      │        │       │              ║
║    │                │  (epoch: 5)      │        │       │              ║
║    │        │◀──────┼──────────────────│        │       │              ║
║    │        │       │                  │        │       │              ║
║    │   5 < 6 ?      │                  │        │       │              ║
║    │   YES ──▶ DROP │                  │        │       │              ║
║    │                │  GridUpdate      │        │       │              ║
║    │                │  (epoch: 6)      │        │       │              ║
║    │        │◀──────┼──────────────────│        │       │              ║
║    │        │       │                  │        │       │              ║
║    │   6 < 6 ?      │                  │        │       │              ║
║    │   NO ──▶ ACCEPT│                  │        │       │              ║
║    │        │       │                  │        │       │              ║
║    │   Inject to    │                  │        │       │              ║
║    │   xterm display│                  │        │       │              ║
║    │                │                  │                │              ║
║    └────────────────┘                  └────────────────┘              ║
║                                                                        ║
║                                                                        ║
║    EPOCH = Monotonically increasing version number                     ║
║                                                                        ║
║    - Frontend increments on resize                                     ║
║    - Frontend passes to backend                                        ║
║    - Backend stores epoch in GridUpdate                                ║
║    - Frontend validates before injection                               ║
║                                                                        ║
╚════════════════════════════════════════════════════════════════════════╝