# Page Mode Scrolling in the tui-logger Smart Widget: Timestamp-Referenced Navigation Explained

> Master page mode scrolling in tui-logger's smart widget. Discover stable timestamp-referenced navigation using LinePointer for efficient log history exploration.

- Repository: [gin66/tui-logger](https://github.com/gin66/tui-logger)
- Tags: deep-dive
- Published: 2026-03-02

---

**Page mode scrolling in the tui-logger Smart Widget anchors the view to the timestamp of the bottom log line when you press Page Up, allowing stable navigation through filtered log history using `LinePointer` references instead of fragile line numbers.**

The `TuiLoggerSmartWidget` in the `gin66/tui-logger` crate provides sophisticated log navigation through **page mode scrolling**, a mechanism that uses timestamp-referenced anchoring to maintain view stability. Unlike standard terminal scrolling that relies on line offsets, this feature preserves your position when filtering criteria change. This article examines the implementation details in the source code and demonstrates how to leverage this feature in your terminal applications.

## What Is Page Mode Scrolling?

Page mode is a special navigation state activated when the user presses **Page Up**. Upon activation, the widget captures the timestamp of the event currently displayed on the bottom line of the visible log area and uses it as a fixed anchor point for subsequent navigation.

While operating in page mode, the following key bindings control movement through the log history:

- **Page Up**: Moves the view approximately half a page upward (toward older events)
- **Page Down**: Scrolls exactly 10 events downward (toward newer events) — functional only while in page mode
- **Escape**: Exits page mode and returns the widget to normal "follow-tail" scrolling behavior

## How Timestamp-Referenced Scrolling Works

The core innovation of page mode is its reliance on **timestamp-referenced scrolling** rather than simple line-number offsets. The system tracks navigation through `LinePointer` structures that identify specific events and sub-lines within multi-line log records.

### The LinePointer Structure

As implemented in the widget state management code, a `LinePointer` contains:

```rust
LinePointer { event_index, subline }

```

This pointer identifies the exact event in the history buffer and the specific sub-line within that event if the log entry spans multiple terminal rows.

### State Persistence Across Filters

The widget stores three critical pointers in `TuiWidgetInnerState` (defined in [`src/widget/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/widget/inner.rs)):

```rust
pub struct TuiWidgetInnerState {
    // ...
    pub(crate) opt_line_pointer_center: Option<LinePointer>,
    pub(crate) opt_line_pointer_next_page: Option<LinePointer>,
    pub(crate) opt_line_pointer_prev_page: Option<LinePointer>,
    // ...
}

```

When `opt_line_pointer_center` is `Some`, the widget operates in page mode. The timestamp of the anchored event remains fixed at the bottom of the view, ensuring that changing filter settings or target-focus toggles does not cause the display to jump. As documented in [`src/lib.rs`](https://github.com/gin66/tui-logger/blob/main/src/lib.rs) (lines 93-96):

> "The timestamp of the event at event history's bottom line is used as reference. This means, changing the filters … should work as expected without jumps in the history."

## Implementation Details

### State Management in inner.rs

State transitions occur in `TuiWidgetState::transition` within [`src/widget/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/widget/inner.rs) (lines 135-138). When key events are received, the state updates the line pointers accordingly:

```rust
PrevPageKey => self.opt_line_pointer_center = self.opt_line_pointer_prev_page,
NextPageKey => self.opt_line_pointer_center = self.opt_line_pointer_next_page,
EscapeKey   => self.opt_line_pointer_center = None,

```

### Pointer Calculation During Rendering

After each render cycle in [`src/widget/standard.rs`](https://github.com/gin66/tui-logger/blob/main/src/widget/standard.rs) (lines 125-139), the widget examines the lines actually drawn (`rev_lines`) to calculate navigation boundaries for the next operation:

```rust
state.opt_line_pointer_next_page = if can_scroll_down {
    rev_lines.first().map(|l| l.0)
} else { None };
state.opt_line_pointer_prev_page = if can_scroll_up {
    rev_lines.last().map(|l| l.0)
} else { None };

```

The first visible line becomes the "next page" marker, while the last visible line becomes the "previous page" marker, but only if scrolling in that direction is possible given the current buffer constraints.

### Entering Page Mode

When the first **Page Up** event occurs, `opt_line_pointer_center` is `None`, triggering the widget to capture the bottom line's pointer as the reference anchor. Subsequent page navigation moves the view relative to this stored timestamp rather than recalculating from the current display offset.

## Practical Usage Examples

### Activating Page Mode

The following example demonstrates handling keyboard input to activate page mode scrolling:

```rust
use tui_logger::*;
use ratatui::crossterm::event::{KeyCode, KeyEvent};

fn main() -> Result<()> {
    init_logger(LevelFilter::Trace)?;
    let state = TuiWidgetState::new();

    // Generate sample log records
    for i in 0..30 {
        info!("record {}", i);
    }

    loop {
        terminal.draw(|f| {
            let smart = TuiLoggerSmartWidget::default()
                .state(&state);
            f.render_widget(smart, f.size());
        })?;

        if let Ok(event) = crossterm::event::read() {
            if let crossterm::event::Event::Key(KeyEvent { code, .. }) = event {
                match code {
                    KeyCode::PageUp => state.transition(TuiWidgetEvent::PrevPageKey),
                    KeyCode::PageDown => state.transition(TuiWidgetEvent::NextPageKey),
                    KeyCode::Esc => state.transition(TuiWidgetEvent::EscapeKey),
                    _ => {}
                }
            }
        }
    }
}

```

### Maintaining View Stability During Filter Changes

Page mode preserves your position when modifying log filters:

```rust
// Navigate to an older log slice
state.transition(TuiWidgetEvent::PrevPageKey);

// Adjust filter settings - the view remains anchored
state.set_level_for_target("my_target", LevelFilter::Warn);
// Display updates to show only Warn+ logs without jumping to the tail

```

### Integration from the Demo Application

The official demo in [`examples/demo.rs`](https://github.com/gin66/tui-logger/blob/main/examples/demo.rs) shows the standard integration pattern:

```rust
match key.code {
    KeyCode::PageUp => state.transition(TuiWidgetEvent::PrevPageKey),
    KeyCode::PageDown => state.transition(TuiWidgetEvent::NextPageKey),
    KeyCode::Esc => state.transition(TuiWidgetEvent::EscapeKey),
    // ... other key handlers
}

```

## Summary

- **Page mode scrolling** activates when you press **Page Up** in a `TuiLoggerSmartWidget`, anchoring the view to the timestamp of the bottom visible line.
- The system uses `LinePointer` structures containing `event_index` and `subline` to reference specific log events rather than relying on volatile line numbers.
- Three pointers manage navigation state: `opt_line_pointer_center` (current anchor), `opt_line_pointer_next_page`, and `opt_line_pointer_prev_page`, all stored in `TuiWidgetInnerState`.
- **Timestamp-referenced scrolling** ensures the view remains stable when changing filters or focus targets, as the anchor point persists regardless of which events are currently visible.
- **Page Down** scrolls 10 events toward newer logs, while **Escape** exits page mode and resumes tail-following behavior.

## Frequently Asked Questions

### How do I enter page mode scrolling in the tui-logger Smart Widget?

Press **Page Up** while the widget has focus. The first press captures the current bottom line's timestamp as an anchor and moves the view up by approximately half a page. Subsequent **Page Up** presses continue navigating backward through the log history, while **Page Down** moves 10 events forward toward newer entries.

### Why does my log view jump when I change filters in normal mode but not in page mode?

In normal "follow-tail" mode, the view always displays the most recent logs, so applying filters immediately redraws from the tail. In page mode, the `opt_line_pointer_center` anchor fixes the view to a specific timestamp. When filters change, the widget recalculates which lines to show around that anchored timestamp without changing the visible time range, preventing jumps.

### What is the difference between scrolling with Page Down in page mode versus normal arrow key scrolling?

In page mode, **Page Down** moves exactly 10 events toward newer logs as defined in the state transition logic. Normal arrow key scrolling typically moves line-by-line or follows the tail continuously. Additionally, **Page Down** only functions after page mode has been activated by **Page Up**; attempting to use it in follow-tail mode has no effect on the anchored view.

### Where is the page mode state stored in the tui-logger codebase?

The navigation state resides in `TuiWidgetInnerState` within [`src/widget/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/widget/inner.rs), specifically in the `opt_line_pointer_center`, `opt_line_pointer_next_page`, and `opt_line_pointer_prev_page` fields. The rendering logic in [`src/widget/standard.rs`](https://github.com/gin66/tui-logger/blob/main/src/widget/standard.rs) updates these pointers after each draw cycle, while `TuiWidgetState::transition` in [`src/widget/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/widget/inner.rs) handles the key event mapping that modifies the center pointer.