Skip to content

fix(desktop): restore terminal scroll position when reattaching#698

Merged
Kitenite merged 2 commits intomainfrom
small-ui-fixes-for-the-soul
Jan 10, 2026
Merged

fix(desktop): restore terminal scroll position when reattaching#698
Kitenite merged 2 commits intomainfrom
small-ui-fixes-for-the-soul

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Jan 9, 2026

Summary

  • Saves terminal viewport scroll position when detaching (e.g., switching tabs)
  • Restores scroll position when reattaching to preserve user's reading position
  • Uses xterm.write callback to ensure scroll restoration happens after content is rendered

Test plan

  • Open a terminal with enough content to scroll
  • Scroll up in the terminal
  • Switch to a different tab
  • Switch back to the terminal tab
  • Verify scroll position is preserved

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Terminal scroll position now persists across session switches, automatically restoring your viewport position when reattaching to a terminal.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 9, 2026

Warning

Rate limit exceeded

@Kitenite has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 25 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between b4cfe85 and c49d943.

📒 Files selected for processing (4)
  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
📝 Walkthrough

Walkthrough

This PR introduces viewport scroll position preservation to terminal sessions. It adds an optional viewportY field to session data structures, captures scroll positions during detach operations, and restores them on reattach, enabling scroll state persistence across terminal session cycles.

Changes

Cohort / File(s) Summary
Terminal TRPC Router
apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
Exposed viewportY in createOrAttach mutation return payload; extended detach input schema with optional viewportY parameter
Terminal Manager Core Logic
apps/desktop/src/main/lib/terminal/manager.ts
Implemented viewport persistence: createOrAttach returns existing session's viewportY; detach accepts and stores optional viewportY on session alongside lastActive timestamp
Terminal Type Definitions
apps/desktop/src/main/lib/terminal/types.ts
Added optional viewportY?: number property to TerminalSession and SessionResult interfaces for scroll position storage
Terminal UI Component
apps/desktop/src/renderer/.../Terminal/Terminal.tsx
Integrated scroll restoration on reattach via xterm API using provided viewportY; captures current scroll position on detach and passes it to mutation

Sequence Diagram

sequenceDiagram
    participant UI as Terminal UI
    participant Router as TRPC Router
    participant Mgr as TerminalManager
    participant XTerm as XTerm Instance

    rect rgb(200, 220, 240)
    Note over UI,XTerm: Reattach Flow - Restore Scroll Position
    UI->>Router: createOrAttach({ paneId, ... })
    Router->>Mgr: createOrAttach(...)
    Mgr->>Mgr: Reuse existing session<br/>Extract viewportY
    Mgr-->>Router: Return SessionResult<br/>{ ..., viewportY }
    Router-->>UI: Return { ..., viewportY }
    UI->>UI: applyInitialState({ ..., viewportY })
    UI->>XTerm: write(scrollback)
    UI->>XTerm: scrollToLine(viewportY)
    end

    rect rgb(240, 200, 220)
    Note over UI,XTerm: Detach Flow - Capture Scroll Position
    UI->>XTerm: Get currentViewportY
    UI->>Router: detach({ paneId, viewportY })
    Router->>Mgr: detach({ paneId, viewportY })
    Mgr->>Mgr: session.viewportY = viewportY<br/>session.lastActive = now()
    Mgr-->>UI: Complete
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A hop through the scrollback we go,
Remembering where we left our view below,
With viewportY saved like a clover so sweet,
Each reattach brings us back to our seat! 🌿

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: restoring terminal scroll position when reattaching, which aligns with the core functionality across all modified files.
Description check ✅ Passed The description includes a summary of changes and a concrete test plan, but lacks sections for 'Related Issues', 'Type of Change', and 'Testing' as specified in the template.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/desktop/src/main/lib/terminal/manager.ts (1)

242-256: Consider clearing viewportY when scrollback is cleared.

The detach implementation correctly stores the viewport position. However, the clearScrollback method (line 258-278) recreates the headless terminal without resetting session.viewportY. This could result in an attempt to restore an invalid scroll position after the buffer is cleared.

♻️ Suggested fix to clear viewportY in clearScrollback
 clearScrollback(params: { paneId: string }): void {
   const { paneId } = params;
   const session = this.sessions.get(paneId);

   if (!session) {
     console.warn(
       `Cannot clear scrollback for terminal ${paneId}: session not found`,
     );
     return;
   }

   // Recreate headless (xterm writes are async, so clear() alone is unreliable)
   session.headless.dispose();
   const { headless, serializer } = createHeadlessTerminal({
     cols: session.cols,
     rows: session.rows,
   });
   session.headless = headless;
   session.serializer = serializer;
+  // Clear saved scroll position since buffer was reset
+  session.viewportY = undefined;
   session.lastActive = Date.now();
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2654b6a and b4cfe85.

📒 Files selected for processing (4)
  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/types.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/types.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for all database operations - never use raw SQL

Files:

  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/types.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/types.ts
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

One component per file - do not create multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (6)
apps/desktop/src/lib/trpc/routers/terminal/terminal.ts (2)

99-99: LGTM - Viewport position properly propagated.

The viewportY is correctly returned from the terminal manager result, enabling scroll position restoration on reattach.


155-155: LGTM - Optional viewportY accepted for persistence.

The detach input schema correctly accepts an optional viewportY parameter, allowing the UI to save scroll position when detaching.

apps/desktop/src/main/lib/terminal/types.ts (1)

22-23: LGTM - Type definitions properly extended.

The optional viewportY fields are correctly added to both TerminalSession and SessionResult with clear documentation. The optional nature is appropriate since new sessions won't have saved scroll positions.

Also applies to: 43-44

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (2)

374-391: LGTM - Scroll restoration correctly implemented.

The use of xterm.write callback ensures the scrollback content is fully rendered before restoring the scroll position. This prevents race conditions where scrollToLine might be called on an incomplete buffer.


570-574: LGTM - Scroll position properly captured on detach.

The viewport position is correctly captured from xterm.buffer.active.viewportY before detachment and passed as an object parameter following the coding guidelines.

apps/desktop/src/main/lib/terminal/manager.ts (1)

44-44: LGTM - Saved scroll position returned on reattach.

When reusing an existing session, the stored viewportY is correctly returned to enable scroll position restoration.

Save the viewport scroll position when detaching a terminal and restore it
when reattaching. This prevents losing the scroll position when switching
between tabs.

- Add viewportY to TerminalSession and SessionResult types
- Store viewportY in session on detach
- Return viewportY from createOrAttach when reattaching
- Use xterm.write callback to restore scroll after content is rendered

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Kitenite Kitenite force-pushed the small-ui-fixes-for-the-soul branch from b4cfe85 to 1f12237 Compare January 10, 2026 00:19
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Kitenite Kitenite merged commit 212aaa8 into main Jan 10, 2026
5 checks passed
@Kitenite Kitenite deleted the small-ui-fixes-for-the-soul branch January 10, 2026 00:44
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

saddlepaddle pushed a commit that referenced this pull request Jan 10, 2026
* fix(desktop): restore terminal scroll position when reattaching

Save the viewport scroll position when detaching a terminal and restore it
when reattaching. This prevents losing the scroll position when switching
between tabs.

- Add viewportY to TerminalSession and SessionResult types
- Store viewportY in session on detach
- Return viewportY from createOrAttach when reattaching
- Use xterm.write callback to restore scroll after content is rendered
Kitenite added a commit that referenced this pull request Jan 14, 2026
Kitenite added a commit that referenced this pull request Jan 14, 2026
* Revert "fix(desktop): scroll terminals to bottom by default (#728)"

This reverts commit 3f1d1ce.

* Revert "fix(desktop): restore terminal scroll position when reattaching (#698)"

This reverts commit 212aaa8.
@coderabbitai coderabbitai Bot mentioned this pull request Jan 31, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant