Skip to content

fix(terminal): use buffered write drain scheduler with visibility fallback#1547

Merged
arnestrickmann merged 1 commit intomainfrom
emdash/feat-terminal-replays-buffered-43h
Mar 20, 2026
Merged

fix(terminal): use buffered write drain scheduler with visibility fallback#1547
arnestrickmann merged 1 commit intomainfrom
emdash/feat-terminal-replays-buffered-43h

Conversation

@arnestrickmann
Copy link
Contributor

@arnestrickmann arnestrickmann commented Mar 20, 2026

Summary

Replaces the raw requestAnimationFrame call in TerminalSessionManager.scheduleWriteDrain() with a new scheduleTerminalWriteDrain utility that handles background-tab scenarios.

Problem

When the app tab is hidden, requestAnimationFrame callbacks are throttled or paused by the browser, causing terminal write drains to stall indefinitely.

Changes

  • Add writeDrainScheduler.ts — schedules drains via requestAnimationFrame when visible, with a setTimeout fallback (48ms) to guarantee delivery even when throttled. Falls back to setTimeout(0) when the document is hidden.
  • Update TerminalSessionManager to use the new scheduler and track a cancelPendingWriteDrain handle, ensuring pending drains are properly cancelled on dispose/reset.

Fixes #1510

Summary by CodeRabbit

  • Refactor

    • Improved terminal write operation scheduling to enhance responsiveness when the window is visible, with optimized timing strategies and refined fallback behavior for edge cases.
  • Tests

    • Added comprehensive test suite for terminal write scheduling with visibility state detection and timer fallback scenarios.

@vercel
Copy link

vercel bot commented Mar 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Mar 20, 2026 6:18am

Request Review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

Added scheduleTerminalWriteDrain utility function to defer terminal write operations, prioritizing requestAnimationFrame when the document is visible and falling back to setTimeout otherwise. Modified TerminalSessionManager to use this scheduler and properly cancel pending writes on disposal.

Changes

Cohort / File(s) Summary
Write Drain Scheduling
src/renderer/terminal/writeDrainScheduler.ts
New scheduler utility that defers write callback execution via requestAnimationFrame (when document is visible), with a 48ms timeout fallback and setTimeout(0) fallback for hidden documents. Returns a cancellation function to prevent callback execution.
Terminal Session Manager Integration
src/renderer/terminal/TerminalSessionManager.ts
Updated to use scheduleTerminalWriteDrain instead of raw requestAnimationFrame. Stores the returned cancellation function and invokes it during dispose() to clean up any pending scheduled writes.
Test Suite
src/test/renderer/writeDrainScheduler.test.ts
New comprehensive test suite covering three scenarios: visible document with RAF execution, hidden document with timeout fallback, and RAF fallback when callback is never invoked. Uses fake timers and snapshot/restore of global APIs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A scheduler hops through frames of light,
When screens are dark, a timeout takes flight,
Write drains flow swift, no playback in sight,
Our terminal's buffered woes set right,
Visibility clears the hopping night! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: replacing raw requestAnimationFrame with a buffered write drain scheduler that includes visibility detection fallback.
Linked Issues check ✅ Passed The PR implements a scheduler that defers write operations based on document visibility, addressing issue #1510's core problem of buffered output replaying when returning from background tabs.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the visibility-aware write drain scheduler and updating TerminalSessionManager to use it, with comprehensive test coverage.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch emdash/feat-terminal-replays-buffered-43h
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link

@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.

🧹 Nitpick comments (1)
src/test/renderer/writeDrainScheduler.test.ts (1)

42-94: Good test coverage for core scenarios.

The tests correctly verify the three main paths: RAF when visible, timeout when hidden, and timeout fallback when RAF stalls. The third test also validates that cancelAnimationFrame is called with the correct handle during cleanup.

Consider adding a test for the cancellation contract (calling the returned function before execution should prevent run() from firing). This would validate the disposal path used by TerminalSessionManager.

💡 Optional: Add cancellation test
+  it('cancellation prevents run from executing', () => {
+    let frameCallback: ((time: number) => void) | null = null;
+    const run = vi.fn();
+
+    globals.document = { visibilityState: 'visible' } as Document;
+    globals.requestAnimationFrame = vi.fn((callback: (time: number) => void) => {
+      frameCallback = callback;
+      return 1;
+    });
+    globals.cancelAnimationFrame = vi.fn();
+
+    const cancel = scheduleTerminalWriteDrain(run);
+
+    cancel();
+
+    expect(globals.cancelAnimationFrame).toHaveBeenCalledWith(1);
+
+    // Simulate RAF firing after cancellation
+    frameCallback?.(16);
+    vi.runAllTimers();
+
+    expect(run).not.toHaveBeenCalled();
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/renderer/writeDrainScheduler.test.ts` around lines 42 - 94, Add a
new test for scheduleTerminalWriteDrain that verifies the cancellation contract:
call scheduleTerminalWriteDrain(run) and immediately invoke the returned cancel
function, then advance timers and/or trigger the stored RAF callback (depending
on visibility) and assert that run was not called and that cancelAnimationFrame
was invoked with the RAF handle when applicable; reference
scheduleTerminalWriteDrain, globals.requestAnimationFrame,
globals.cancelAnimationFrame and the returned cancel function to locate the code
to update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/test/renderer/writeDrainScheduler.test.ts`:
- Around line 42-94: Add a new test for scheduleTerminalWriteDrain that verifies
the cancellation contract: call scheduleTerminalWriteDrain(run) and immediately
invoke the returned cancel function, then advance timers and/or trigger the
stored RAF callback (depending on visibility) and assert that run was not called
and that cancelAnimationFrame was invoked with the RAF handle when applicable;
reference scheduleTerminalWriteDrain, globals.requestAnimationFrame,
globals.cancelAnimationFrame and the returned cancel function to locate the code
to update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1213744e-4e7c-4801-aaf6-f2d7f7a53c0c

📥 Commits

Reviewing files that changed from the base of the PR and between ddbbd14 and 90715c9.

📒 Files selected for processing (3)
  • src/renderer/terminal/TerminalSessionManager.ts
  • src/renderer/terminal/writeDrainScheduler.ts
  • src/test/renderer/writeDrainScheduler.test.ts

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@arnestrickmann
Copy link
Contributor Author

fixes #1531

@arnestrickmann arnestrickmann merged commit b75ffad into main Mar 20, 2026
7 checks passed
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.

Bug: Terminal replays buffered output when switching back to Emdash

1 participant