Description
Implement ANSI scroll region architecture for oclite — scrollable conversation area with fixed bottom panel.
Problem
After removing alt screen buffer (#66), oclite writes everything append-only to the main terminal buffer. This means:
- ❌ Cannot rewrite tool status lines once scrolled
- ❌ Cannot have a persistent status bar
- ❌ Cannot show live metrics during streaming
- ❌ Cannot have a fixed input area
Solution: ANSI Scroll Regions
Use \x1b[{top};{bottom}r to define a scroll region that covers most of the terminal, with 2-3 fixed lines at the bottom.
Layout:
┌─────────────────────────────────────┐
│ Scrollable conversation area │ ← Scroll region (rows 1 to N-3)
│ User messages, agent responses │
│ Tool output, markdown text │
│ ...scrolls naturally... │
│ │
├─────────────────────────────────────┤
│ ◆ Thinking · 5s · 3/5 tools │ ← Fixed status line (row N-2)
│ project-manager ❯ █ │ ← Fixed input line (row N-1)
│ Esc cancel · Shift+Tab agents │ ← Fixed hint bar (row N)
└─────────────────────────────────────┘
Requirements
Scroll Region Setup
- On startup: query terminal size, set scroll region
\x1b[1;{rows-3}r
- Handle
SIGWINCH (terminal resize) — recalculate and reset scroll region
- On cleanup: reset scroll region to full terminal
\x1b[r
Fixed Bottom Panel (3 lines)
- Status line: During streaming shows spinner + elapsed time + tool count + token count. During idle shows nothing or session info.
- Input line: Prompt + user input (
project-manager ❯ █)
- Hint bar: Context-sensitive keyboard shortcuts (
Esc cancel · Shift+Tab agents · /help)
Writing to Scroll Region
- All conversation output (text, tool display, errors) writes to scroll region
- Scroll region scrolls naturally — old content preserved in scroll buffer
- New content appends at bottom of scroll region, pushing old content up
Writing to Fixed Panel
- Use
\x1b[s (save cursor) before updating fixed lines
- Move to fixed line with
\x1b[{row};1H (absolute position)
- Write content + clear to end of line
- Use
\x1b[u (restore cursor) to return to scroll region
- Update fixed panel without disturbing scroll region content
Terminal Resize Handling
- Listen for
SIGWINCH signal
- Recalculate scroll region boundaries
- Redraw fixed bottom panel
- Preserve scroll position in conversation area
Implementation Plan
- Create
src/cli/lite/layout.ts — scroll region manager
- Modify
index.ts — use layout manager for all writes
- Modify
spinner.ts — render in status line instead of scroll region
- Modify
bottombar.ts — render in fixed input/hint lines
Depends On
Blocks
Technical Reference
- ANSI scroll region:
\x1b[{top};{bottom}r (DEC origin mode)
- Terminal size:
process.stdout.rows, process.stdout.columns
- Resize signal:
process.on('SIGWINCH', handler)
- Save/restore cursor:
\x1b[s / \x1b[u
- Absolute position:
\x1b[{row};{col}H
Files
- New:
packages/opencode/src/cli/lite/layout.ts
- Modified:
packages/opencode/src/cli/lite/index.ts
- Modified:
packages/opencode/src/cli/lite/spinner.ts
- Modified:
packages/opencode/src/cli/lite/bottombar.ts
Description
Implement ANSI scroll region architecture for oclite — scrollable conversation area with fixed bottom panel.
Problem
After removing alt screen buffer (#66), oclite writes everything append-only to the main terminal buffer. This means:
Solution: ANSI Scroll Regions
Use
\x1b[{top};{bottom}rto define a scroll region that covers most of the terminal, with 2-3 fixed lines at the bottom.Layout:
Requirements
Scroll Region Setup
\x1b[1;{rows-3}rSIGWINCH(terminal resize) — recalculate and reset scroll region\x1b[rFixed Bottom Panel (3 lines)
project-manager ❯ █)Esc cancel · Shift+Tab agents · /help)Writing to Scroll Region
Writing to Fixed Panel
\x1b[s(save cursor) before updating fixed lines\x1b[{row};1H(absolute position)\x1b[u(restore cursor) to return to scroll regionTerminal Resize Handling
SIGWINCHsignalImplementation Plan
src/cli/lite/layout.ts— scroll region managerindex.ts— use layout manager for all writesspinner.ts— render in status line instead of scroll regionbottombar.ts— render in fixed input/hint linesDepends On
Blocks
Technical Reference
\x1b[{top};{bottom}r(DEC origin mode)process.stdout.rows,process.stdout.columnsprocess.on('SIGWINCH', handler)\x1b[s/\x1b[u\x1b[{row};{col}HFiles
packages/opencode/src/cli/lite/layout.tspackages/opencode/src/cli/lite/index.tspackages/opencode/src/cli/lite/spinner.tspackages/opencode/src/cli/lite/bottombar.ts