Skip to content

getViewport() returns corrupted data when viewport spans multiple pages #139

Description

@darrinm

Description

renderStateGetViewport in the WASM build resolves the viewport top-left position independently for each row using pages.pin(.active). When the viewport spans multiple internal pages, this per-row resolution can produce inconsistent results, leading to corrupted viewport data.

Additionally, scrollbackLimit is passed as a line count but Terminal.init() interprets max_scrollback as bytes, causing the scrollback buffer to be much smaller than intended and making the page-spanning condition occur more frequently.

Symptoms

  • getScrollbackLength() drops unexpectedly (e.g., 498 → 269) after repeated writes
  • getViewport() returns rows with content from different terminal lines merged together
  • Both getViewport() and getLine() return the same wrong data
  • Corruption frequency depends on column width:
    • cols=80: OK
    • cols=120: CORRUPT
    • cols=130: CORRUPT
    • cols=140: OK
    • cols=160: scrollback drops but viewport may appear OK (merge lands on empty rows)

Root cause

The native Ghostty renderer reads cell data from cached row pins in RenderState.row_data (built during update()), which guarantees consistent page resolution across all viewport rows. The WASM renderStateGetViewport instead calls pages.pin(.active) per-row, which can resolve to different pages inconsistently when the viewport straddles a page boundary.

Reproduction

  1. Create a terminal at cols=130, rows=39, scrollback=10000
  2. Repeatedly write ~68 lines of escape-heavy output (SGR 256-color, truecolor, attributes)
  3. After ~8 repetitions, call getScrollbackLength() and getViewport()
  4. Scrollback length will drop and/or viewport rows will contain merged content

Expected behavior

getViewport() should return consistent, correct row data regardless of internal page layout. scrollbackLimit should be interpreted consistently between the JS config and WASM init.

Fix

Draft PR with fix and regression tests: #133

Metadata

Metadata

Assignees

Labels

acceptedHuman-approved for implementationbugConfirmed or likely defecttriage:doneMux triage report has been posted

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions