Skip to content

Session Restart Semantics

This article documents how terminal state should behave when a host restarts a session in the same terminal control. It compares Ghostty, xterm.js, Windows Terminal, and RoyalTerminal because the visible behavior is not just "clear the screen". A restart has to account for primary and alternate buffers, parser state, terminal modes, keyboard and mouse protocols, cursor state, scrollback, graphics, and host-owned transport lifetime.

RoyalTerminal uses the term "session restart" for a host-level operation:

  1. Stop or replace the active transport.
  2. Reset terminal processor state so the next process starts from defaults.
  3. Optionally preserve previous primary-buffer history.
  4. Start a new transport in the same TerminalControl.

Reference terminal projects do not all expose that exact host-level API. Their relevant behavior comes from the same lower-level operations RoyalTerminal must compose: full reset, alternate-screen entry and exit, scrollback clear, viewport clear, input-mode reset, and renderer state refresh.

Reference Sources

The implementation comparison is based on these reference code paths:

ProjectRelevant source
GhosttyTerminal.fullReset, ModeState.reset, Screen.reset, StreamHandler.fullReset
xterm.jsCoreTerminal.reset, InputHandler.fullReset, InputHandler private mode set/reset, BufferSet.activateNormalBuffer
Windows TerminalAdaptDispatch::HardReset, AdaptDispatch::_SetAlternateScreenBufferMode, Terminal::UseAlternateScreenBuffer, Terminal::UseMainScreenBuffer, ControlCore::ClearBuffer
RoyalTerminalTerminalControl.PrepareTerminalForSessionStart, BasicVtProcessor.PrepareForNewSession, GhosttyVtProcessor.PrepareForNewSession, TerminalScreen.MoveViewportToScrollbackAndClear

Core Invariants

All compared implementations follow the same conceptual model:

InvariantWhy it matters
The primary buffer owns persistent shell history.Shell prompts and command output belong here and can safely survive a session restart when the host opts into preservation.
The alternate buffer is app-owned and transient.Full-screen applications such as mc, btop, vim, less, and terminal UIs write here. If the app is killed or the session is restarted before it sends its exit sequence, the terminal must still return to primary state.
Private modes are process-visible state.Cursor keys, keypad, bracketed paste, mouse protocols, focus events, synchronized output, and alternate-screen state must not leak into the next process.
Parser state must be reset.A restart must not resume in the middle of CSI, OSC, DCS, UTF-8, or similar parser payload state.
Scrollback clear and restart are different operations.CSI 3 J clears scrollback. A preserved primary-screen restart keeps existing history and moves the current primary viewport into history before clearing the live viewport; a preserved alternate-screen restart restores primary first and keeps that restored primary prompt area visible.
Cursor visibility is viewport-relative.Rendering must decide whether the cursor is visible by comparing the cursor's absolute row against the visible viewport, not by requiring the viewport to be at the live bottom.

The important user-facing result is that restarting a session while mc or btop is still active should not preserve the application screen as shell history. The terminal should discard the alternate-screen UI, restore the primary shell buffer, then prepare the new session from that primary state.

Ghostty Behavior

Ghostty's terminal reset path is centered around Terminal.fullReset.

Full Reset

Terminal.fullReset performs these state transitions:

State areaReset behavior
Active screenSwitches to the primary screen.
Alternate screenRemoves the alternate screen object.
Primary screen contentsCalls Screen.reset, clearing the active pages and returning the cursor to the top-left page pin.
ModesCalls modes.reset, restoring default values and clearing saved mode state.
Dirty flagsReinitializes flags and marks the screen for a full redraw.
Tab stopsRestores regular tab stops using the terminal tab interval.
Previous characterClears previous-character state used by parser/print behavior.
Working directory and titleClears retained working directory and title strings.
Status displayReturns to the main display.
Scrolling regionRestores top, bottom, left, and right margins to the whole terminal.

Screen.reset also resets screen-local state:

Screen stateReset behavior
CursorReinitializes cursor state at the new page origin.
Saved cursorClears saved cursor state.
Character setsRestores default charset state.
Kitty keyboardClears Kitty keyboard flags and stack.
Protected modeReturns protection mode to off.
SelectionClears selection.
Kitty graphicsDeletes stored graphics when the feature is enabled.
Semantic promptDisables semantic prompt state.

ModeState.reset returns mode values to configured defaults and clears the saved-mode map used by save/restore mode sequences.

Mode Defaults

Ghostty's mode table defines the default state. The important defaults for restart behavior are:

ModeDefault
ANSI insert modeOff
ANSI send/receive modeOn
ANSI linefeed/newline modeOff
DEC application cursor keysOff
DEC origin modeOff
DEC wraparoundOn
DEC cursor visibleOn
DEC alternate screen modes 47, 1047, 1049Off
DEC application keypadOff
DEC backarrow key modeOff
Mouse tracking and encodingsOff, except alternate scroll mode defaults on
Bracketed pasteOff
Synchronized outputOff
Color scheme reportsOff
In-band size reportsOff
Ignore keypad with numlockOn
Alt escape prefixOn

Termio Side Effects

Ghostty's StreamHandler.fullReset wraps terminal.fullReset and also:

State areaReset behavior
Mouse cursor shapeReturns mouse shape to text.
Color scheme reportEmits a color-scheme report because reset can affect palette state.
Progress reportingRemoves progress state.

These are not VT grid cells, but they are still visible integration state. A host that builds on Ghostty must not preserve stale values from the old app.

Alternate Screen

Ghostty treats alternate screen as application-owned state. The reset path switches back to the primary screen and removes alternate state. That is the behavior RoyalTerminal follows for preserved restarts.

For explicit clear behavior, Ghostty supports:

SequenceBehavior
CSI 3 JErase scrollback/history.
CSI 22 JScroll-complete extension: move the current active viewport into scrollback and clear the live screen.

RoyalTerminal uses CSI 22 J semantics as the native primitive for preserving a completed session's visible primary viewport into history.

xterm.js Behavior

xterm.js separates terminal reset into service resets and buffer activation.

Full Reset

InputHandler.fullReset resets the parser and fires a reset request. CoreTerminal.reset then resets:

ServiceReset behavior
Input handlerResets current attributes and erase attributes to defaults.
Buffer serviceResets the normal and alternate buffer set.
Charset serviceRestores charset designations.
Core serviceResets core DEC private modes and cursor initialization state.
Mouse state serviceResets mouse protocol and encoding state.

This mirrors the same separation RoyalTerminal uses: parser and modes belong to the processor, while scrolling and screen rows belong to the screen model.

Alternate Screen

xterm.js implements alternate screen through BufferSet.activateAltBuffer and BufferSet.activateNormalBuffer.

Entering alternate screen:

State areaBehavior
Alternate bufferFilled to viewport size with erase attributes.
Cursor positionAlternate x and y copy the normal buffer position.
Kitty keyboardMain flags are saved and alternate flags are restored when Kitty keyboard extension is enabled.
Active bufferSwitches active buffer to alternate.
ScrollbarRequests scrollbar sync.

Leaving alternate screen:

State areaBehavior
Normal bufferBecomes active.
Cursor positionNormal x and y copy the alternate position, then 1049 restore cursor can apply saved cursor state.
Alternate bufferMarkers are cleared and the buffer is cleared.
Kitty keyboardAlternate flags are saved and main flags are restored.
RefreshRows are refreshed and scrollbar is synchronized.

The important part for restart is that the alternate buffer is not a history store. It is cleared when normal buffer is reactivated.

Private Mode Reset

xterm.js explicitly resets private modes that affect process interaction:

Mode areaReset behavior
Application cursor keysOff.
Origin modeOff and cursor home is reset.
WraparoundCan be reset and queried.
Reverse wraparoundOff.
Application keypadOff.
Mouse protocolsActive protocol reset to none.
Mouse encodingsEncoding reset to default.
Focus eventsOff.
Cursor visibilityDECTCEM controls hidden/visible state.
Alternate buffer modesReturn to normal buffer.
Bracketed pasteOff.
Synchronized outputOff.

RoyalTerminal's managed and native restart paths reset the same mode categories so input encoding and paste behavior do not leak from one process into the next.

Scrollback and Cursor Visibility

xterm.js uses absolute buffer coordinates for cursor visibility:

  1. Cursor absolute row is ybase + y.
  2. Viewport top is ydisp.
  3. Cursor is visible when absoluteY - ydisp is inside the viewport rows.

This is why a cursor can be rendered while viewing preserved restart history when the cursor row is actually in the visible row range. RoyalTerminal follows this model in its renderer synchronization.

Windows Terminal Behavior

Windows Terminal splits behavior across the parser dispatch layer and terminal core.

Hard Reset

AdaptDispatch::HardReset performs a reset to initial state. Key behavior:

State areaReset behavior
Parser/state machineReset through the state machine before dispatch.
Active screen contentsOptional erase of display and scrollback when reset is invoked with erase.
SGR and character setsSoft reset restores character set designations and attributes.
Code page and C1 controlsCode page reset and C1 parsing/sending disabled.
Render settingsRestored to default startup settings.
CursorMoves to home when erasing; otherwise cursor position is preserved for the next shell prompt.
Linefeed modeResets linefeed if the input mode owns it.
Input modesTerminalInput.ResetInputModes returns input modes to defaults.
Bracketed pasteOff.
Cursor blinkOn.
Tab stopsClears current tab stops and sets every eight columns.
Soft fontClears renderer soft font and font buffer.
Internal modesRestores internal modes to initial state.
Macro bufferClears and releases macro state.
ConPTY integrationInjects a reset marker so ConPTY can re-enable modes it requires.

Windows Terminal distinguishes a reset that erases buffers from a reset that preserves contents. That distinction maps to RoyalTerminal's default restart versus preserve-history restart.

Alternate Screen

AdaptDispatch::_SetAlternateScreenBufferMode maps private mode 1049 to core buffer operations.

Entering alternate screen:

State areaBehavior
CursorSave cursor state.
Alternate bufferCreate a viewport-sized alternate buffer.
Cursor style and visibilityCopy cursor size, type, visibility, and blink into the alternate buffer.
Cursor positionConvert main-buffer position to viewport-relative alternate-buffer position.
InputNotify TerminalInput that the alternate buffer is active.
Selection and linksClear selection and update URL detection.
Scrollbars and redrawNotify scroll event and trigger redraw.

Leaving alternate screen:

State areaBehavior
Active bufferMain buffer becomes active.
Alternate bufferDestroyed after cursor data is read.
Deferred resizeApplied before cursor state is copied back.
Cursor style and visibilityMain cursor adopts current alternate cursor style, visibility, and blink.
Cursor positionAlternate viewport-relative position is converted back to main-buffer coordinates.
InputNotify TerminalInput that main screen is active.
Scrollbars and redrawNotify scroll event and redraw active buffer.

This matches xterm's high-level model: alternate buffer is discarded when leaving it, and main buffer becomes the durable screen.

Clear Buffer

Windows Terminal's ControlCore::ClearBuffer separates three operations:

Clear typeBehavior
ScrollbackEmits CSI 3 J.
ScreenKeeps the cursor row, erases above and below it, and moves the cursor row to the top.
AllClears scrollback plus screen contents while preserving the cursor row behavior.

RoyalTerminal exposes separate ClearScrollback() and restart preservation operations for the same reason: user intent differs between clearing old history and restarting a session while retaining a transcript.

RoyalTerminal Behavior

RoyalTerminal has two VT processors but one session host contract.

ComponentResponsibility
TerminalControlOwns Avalonia host state, renderer, session service, scroll data, selection, and transport start/stop orchestration.
TerminalSessionServiceConnects a transport or endpoint to the active processor, routes data, and exposes mode/input state.
BasicVtProcessorFully managed parser and screen updater.
GhosttyVtProcessorNative Ghostty-backed parser and screen updater.
TerminalScreenScrollback-aware row model, primary/alternate backing buffers, raster graphics, Kitty graphics, viewport position, and dirty state.

Restart Entry Point

TerminalControl.StartSessionAsync(options, preserveScrollback) calls PrepareTerminalForSessionStart before the new transport starts.

The control first:

  1. Flushes pending transport output.
  2. Clears selection.
  3. Resets cursor blink phase.
  4. Records previous row and scroll metrics.
  5. Locks the screen model.

Then it chooses one of three paths:

Processor capabilitypreserveScrollbackBehavior
Processor implements ITerminalSessionHistoryControllertrue or falseDelegate to PrepareForNewSession(preserveScrollback).
Processor does not implement the history controllertrueIf primary is active, move the primary viewport into scrollback, clear the live viewport, then reset processor state. If alternate screen is active, restore primary first, discard inactive alternate rows, skip the primary viewport clear, then reset processor state.
Processor does not implement the history controllerfalseClear all screen state and reset processor state.

After screen mutation, TerminalControl synchronizes scroll state so a preserved restart remains pinned to the live bottom. This prevents a parent scroll viewer from replaying an older top-anchored offset after the terminal extent changes.

Managed VT Restart

BasicVtProcessor.PrepareForNewSession calls the internal reset path with either ClearAll or PreserveScrollback.

The managed restart reset covers:

State areaReset behavior
Cursor positionColumn and row set to zero for primary-screen restart; restored from the saved primary cursor for alternate-screen restart.
Delayed wrapCleared.
Parser stateReturns to ground state.
CSI parameters and markersCleared.
OSC bufferCleared, including discard state.
DCS bufferCleared, including discard state.
UTF-8 and graphics contextLast graphic codepoint and line drawing state reset.
Scroll regionRestored to full viewport.
Alternate screenSwitches to primary if active.
Inactive alternate bufferDiscarded.
Auto-wrapRestored on.
Cursor visibilityRestored visible.
Origin modeOff.
Application cursor keysOff.
Application keypadOff.
Backarrow key modeOff.
Save cursor modeOff.
Bracketed pasteOff.
Win32 input modeOff.
Keyboard action modeOff.
Send/receive modeOn.
Extended DEC modesReset to defaults, including alternate scroll, ignore keypad with numlock, and alt escape prefix.
Sixel display modeOff.
Insert modeOff.
Linefeed/newline modeOff.
Cursor styleRestored to default blinking block.
Saved style and hyperlink stateCleared.
Current hyperlinkCleared.
Kitty keyboardMain and alternate flags cleared and stacks emptied.
SGR attributesReset to defaults.
Tab stopsReinitialized.

If history is preserved and the active screen is primary, the reset then calls TerminalScreen.MoveViewportToScrollbackAndClear. If history is preserved and restart begins in alternate screen, the processor first exits alternate screen with 1049-style primary cursor restoration, discards the transient application buffer, and keeps the restored primary viewport live. It deliberately does not call MoveViewportToScrollbackAndClear on that restored primary prompt area, because doing so would hide the prompt that alternate-screen exit just exposed.

Managed Screen Preservation

TerminalScreen.MoveViewportToScrollbackAndClear does this on the primary buffer:

  1. Discards transient resize rows.
  2. Resets scroll offset to the live bottom.
  3. Counts non-empty viewport rows.
  4. Appends enough blank rows to move those rows into scrollback.
  5. Trims scrollback to the configured limit.
  6. Clears the live viewport rows.
  7. Clears raster graphics in the live viewport rectangle.
  8. Clears Kitty graphics for the new live session.
  9. Marks the screen dirty.

The preserved history keeps TerminalRow and TerminalCell instances, so text, colors, attributes, underline style, decorations, and hyperlink ids remain available for copied rows.

If the screen is still in alternate-buffer mode, the low-level screen primitive only clears the alternate viewport. The processor and control restart paths therefore switch back to primary before using this primitive for primary-screen preserved restart, and they skip it for interrupted alternate-screen restarts.

Native Ghostty Restart

GhosttyVtProcessor.PrepareForNewSession(true) cannot call Ghostty's native full reset directly because a full reset clears the preserved history. Instead it composes Ghostty's existing parser behavior with two restart paths:

Starting stateSequence categoryPurpose
Primary screen activeCSI 22 JMove the active primary viewport into scrollback and clear the live viewport.
Primary screen activeANSI and DEC private mode resetReset keyboard action, insert, linefeed/newline, application cursor keys, origin, column, reverse video, mouse, focus, alternate-screen, bracketed paste, synchronized output, color report, in-band report, and Win32 input modes.
Primary screen activeDEC private default-on modesRestore wraparound, cursor visible, alternate scroll, ignore keypad with numlock, and alt escape prefix.
Primary screen activeKitty keyboard, margins, cursor, SGR, charsetsReset Kitty keyboard state, reset scroll region, move cursor home, restore default cursor style, reset attributes, restore G0/G1 ASCII charsets, and invoke G0.
Alternate screen activeDECRST 1049, 1047, 47Return to the primary buffer and restore the cursor saved when the alternate screen was entered.
Alternate screen activeNative screen refreshCapture the restored primary cursor position from Ghostty before any mode reset sequence can move it.
Alternate screen activeANSI and DEC private mode resetReset the same process-visible modes as the primary path without applying CSI 22 J to the restored primary prompt area.
Alternate screen activeMargins, cursor, SGR, charsetsReset scroll region, restore the captured primary cursor, restore default cursor style, reset attributes, restore G0/G1 ASCII charsets, and invoke G0.

After the native parser processes the sequence, RoyalTerminal reapplies optional native features, theme colors, terminal effects, mouse encoder state, and sixel overlay state. It then refreshes screen state and raises mode-change notifications if needed.

Non-Preserving Restart

When preserveScrollback is false, RoyalTerminal clears the terminal instead of moving current rows into history:

ProcessorBehavior
ManagedBasicVtProcessor resets state and calls TerminalScreen.ClearAll.
Native GhosttyGhosttyVtProcessor calls its Reset path, which maps to native reset behavior and refreshes state.
Fallback processorTerminalControl calls TerminalScreen.ClearAll and then IVtProcessor.Reset.

This is the correct behavior for hosts that expect a clean terminal every time a connection starts.

State Comparison Matrix

StateGhosttyxterm.jsWindows TerminalRoyalTerminal
Parser stateFull reset clears parser-side state through stream/terminal reset.Parser reset fires request reset; input handler reset clears attributes.State machine reset plus dispatch reset.Managed clears parser buffers and markers; native uses Ghostty parser reset sequence or native reset.
Primary bufferReset clears active primary; scroll-complete can move viewport into scrollback; 1049l restores the saved primary cursor.Normal buffer reset or clear depending operation; 1049l activates normal buffer and restores cursor.Main buffer persists across alternate screen; hard reset can erase.Preserved primary restart moves the primary viewport into scrollback; preserved alternate restart restores the primary buffer and keeps it live; clean restart clears all.
Alternate bufferFull reset switches primary and removes alternate.Normal activation clears alternate buffer.Main activation destroys alternate buffer.Managed/native restart exits alternate first; fallback control restores primary first.
ScrollbackCSI 3 J clears history; CSI 22 J preserves viewport into history.ED 3 clears scrollback; clear can keep prompt row.CSI 3 J clears scrollback; clear types distinguish scrollback/screen/all.ClearScrollback clears history only; preserved primary restart retains history and clears live viewport; preserved alternate restart retains history and keeps the restored primary viewport live.
Cursor positionReset homes cursor; scroll-complete homes live viewport in RoyalTerminal native sequence; 1049l restores saved primary cursor.Cursor is buffer-relative; 1049 restore applies saved cursor.Cursor saved/restored for alt screen; hard reset homes when erasing.Primary restart homes the blank live session; alternate restart restores the saved primary cursor.
Cursor visibility and styleMode defaults restore visible cursor and reset cursor state.DECTCEM and cursor options reset through core/input state.Cursor visibility/blink copied through alt transition and reset by hard reset.Managed resets visible and default style; native sequence resets visible and cursor style.
Keyboard modesMode state reset restores defaults.Application cursor/keypad reset through core service.TerminalInput.ResetInputModes restores defaults.Managed/native reset application cursor, keypad, backarrow, Win32 input, and Kitty keyboard state.
Mouse modesMode defaults reset tracking and encodings.Mouse state service reset clears protocol/encoding.Terminal input modes reset mouse protocols and encodings.Managed/native reset mouse tracking, encodings, focus, and pressed button state.
Bracketed pasteDefault off.Reset off.System mode reset off.Managed/native reset off.
Focus eventsDefault off.Reset off.Input mode reset off, with ConPTY reinjection as needed.Managed/native reset off.
Synchronized outputDefault off.Reset off.Render setting reset and synchronized output notification.Managed/native reset off.
Color and SGR attributesScreen/cursor styles reset; colors may be restored/report updated.Input handler restores default attributes.Soft reset restores rendition, render settings reset.Managed resets SGR; native writes SGR reset and reapplies host theme.
Character setsScreen reset clears charset.Charset service reset.Soft reset resets character set designations.Managed resets G0/G1 and line drawing; native writes G0/G1 ASCII and shift-in.
Tab stopsReset every tab interval.Buffer/core reset restores defaults.Hard reset sets every eight columns.Managed reinitializes tabs; native sequence leaves Ghostty parser defaults or native reset path handles clean restart.
GraphicsKitty graphics reset on screen reset.Not the same native graphics model.Sixel parser soft reset and soft font clear.Managed clears live Kitty/raster graphics for primary restart; alternate-screen graphics are discarded with the alternate buffer; native syncs sixel overlay and clears live graphics through parser/screen refresh.
SelectionCleared on reset or screen switch.Selection service tracks active buffer.Selection cleared when switching buffers.TerminalControl clears selection before restart.
Scroll viewportScrollbar reflects active buffer.ydisp tracks viewport; cursor visibility is absolute row minus ydisp.Scroll event notified after buffer switch.Scroll data is resynchronized and pinned to live bottom after preserved restart.

Apps Such As mc And btop

Full-screen terminal applications normally enter alternate screen with CSI ? 1049 h and exit with CSI ? 1049 l. If the process exits cleanly, the app sends the exit sequence and the terminal returns to the primary buffer before the next prompt.

A host-level restart can interrupt the app before it sends the exit sequence. Correct terminal behavior still must restore the primary buffer:

  1. Detect or force alternate-screen exit.
  2. Discard the alternate app-owned buffer.
  3. Restore primary shell content and the saved primary cursor.
  4. Keep that restored primary prompt area visible instead of applying a scroll-complete clear to it.
  5. Reset modes that the app may have enabled.
  6. Start the next process from that primary-buffer state.

RoyalTerminal covers this in both processor paths:

PathBehavior
ManagedBasicVtProcessor.ResetInternal exits alternate screen through the same saved-primary-cursor path as 1049l, discards the inactive alternate buffer, and skips MoveViewportToScrollbackAndClear for that restored primary prompt area.
Native GhosttyThe preserved restart sequence exits alternate screen first, refreshes the restored native primary cursor, resets process-visible modes, and avoids CSI 22 J for interrupted alternate-screen restarts.
Fallback control pathTerminalControl switches TerminalScreen to primary and discards inactive alternate rows, then skips the viewport preservation primitive when the restart began in alternate screen.

This means a restart from mc, btop, or a similar TUI restores the shell transcript and primary cursor that launched the app, not the application's alternate-screen UI.

What RoyalTerminal Preserves

With preserveScrollback: true, RoyalTerminal preserves:

StatePreserved?Notes
Existing primary scrollback rowsYesKept up to ScrollbackLimit.
Current primary viewport textYesMoved into scrollback before the new live viewport is cleared for primary-screen restarts; kept live when restored after interrupted alternate-screen restarts.
Cell styling in preserved rowsYesText, foreground, background, attributes, underline style, decorations, and hyperlink ids remain in row cells.
Alternate-screen app UINoIt is transient app-owned state.
Live viewport for the new processDependsPrimary-screen preserved restart starts with blank live rows; interrupted alternate-screen restart starts from the restored primary prompt area.
Host theme and renderer settingsYesThese are control configuration, not process state.
Transport processNoThe old transport is stopped/replaced; restart does not resurrect the app.
Parser state and modesNoReset so the new process starts from terminal defaults.
SelectionNoCleared before restart.
Mouse pressed-button stateNoReset to avoid stale drag/button reporting.
Cursor blink phaseNoReset so the cursor starts visible according to current cursor settings.

Validation Contracts

The restart behavior is covered by focused tests:

Test areaCoverage
TerminalSessionHistoryTestsScreen primitives, managed CSI 3 J, managed CSI 22 J, managed preserved restart from alternate-screen apps, and managed mode reset.
TerminalControlTestsStartSessionAsync(..., preserveScrollback), scroll pinning after restart, fallback primary-buffer restore, explicit scrollback clear, and cursor visibility while scrolled.
GhosttyVtProcessorTestsNative scrollback clear, native preserved restart, native alternate-screen restart, and native process-visible mode reset.
Demo ViewModel/controller testsRestart Session, Preserve History, and Clear History command routing through ViewModels and interactions.

These tests encode the same reference decision documented above: restart must reset process-owned terminal state, restore primary-buffer ownership, and preserve only durable primary history when requested.

MIT Licensed