Skip to content

fix(desktop): retry terminal repaint after focus restore - #3322

Closed
surlymochan wants to merge 2 commits into
ParadevAI:mainfrom
surlymochan:fix/terminal-refocus-recovery
Closed

surlymochan wants to merge 2 commits into
ParadevAI:mainfrom
surlymochan:fix/terminal-refocus-recovery

Conversation

@surlymochan

@surlymochan surlymochan commented Apr 10, 2026 •

Copy link
Copy Markdown

Summary

  • add a short recovery burst when a terminal becomes visible again after focus / visibility changes
  • keep the existing throttle logic, but retry repaint/fit twice after the initial restore attempt
  • add a regression test that models the new burst behavior

Why

The existing terminal restore path already does the right kinds of work on refocus:

  • clear the WebGL texture atlas
  • re-fit the terminal to its container
  • force an xterm refresh

The remaining problem is timing. In practice, one refocus recovery can still happen slightly before the terminal container has fully settled after the app/window/workspace becomes visible again. That leaves stale blank space or stale terminal/TUI styling until some later repaint happens.

Running a short recovery burst fixed the repro locally:

  • immediate restore
  • second restore after ~120ms
  • third restore after ~260ms

This keeps the change small and local to the focus/visibility recovery path, while giving xterm another chance to repaint once layout has actually stabilized.

Test plan

  • bun test ./src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts

Related

Summary by CodeRabbit

  • Bug Fixes

    • Improved terminal reconnection reliability with an immediate recovery attempt plus additional quick follow-up retries.
    • Enhanced cleanup to cancel pending recovery retries when the component unmounts.
  • Tests

    • Added test coverage verifying burst-style recovery triggers the immediate attempt plus follow-up retries.
    • Updated a throttle test description to label it as a regression test.

@coderabbitai

coderabbitai Bot commented Apr 10, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1705d637-cc61-442d-b071-1dae353495fb

📥 Commits

Reviewing files that changed from the base of the PR and between 98a8470 and 005f69f.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts

📝 Walkthrough

Walkthrough

The PR adds a "recovery burst" to terminal reattachment: on visibility/focus restore it runs recovery immediately and schedules two follow-up recovery attempts (~120ms and ~260ms), and it tracks/clears those timeout IDs during cleanup.

Changes

Cohort / File(s) Summary
Test: scheduler & lifecycle
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts
Extended test scheduler model with burstTimeouts and scheduleBurst; added a regression test verifying three recovery invocations (immediate, ~120ms, ~260ms) and updated test narrative.
Runtime: terminal lifecycle
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts
Added reattachRecovery.burstTimeouts; implemented scheduleRecoveryBurst(forceResize) to call immediate recovery and schedule two delayed retries, updated visibility/focus handlers to use burst scheduling, and clear burst timeouts on unmount/cleanup.

Sequence Diagram(s)

sequenceDiagram
    participant Window
    participant TerminalHook
    participant Scheduler
    participant RecoveryCallback

    Window->>TerminalHook: visibilitychange / focus
    TerminalHook->>Scheduler: scheduleRecoveryBurst(forceResize)
    Scheduler->>RecoveryCallback: invoke immediate reattachRecovery
    note right of Scheduler: schedule timeouts (120ms, 260ms)
    Scheduler->>Scheduler: store timeout IDs in burstTimeouts
    alt not unmounted after 120ms
        Scheduler->>RecoveryCallback: invoke reattachRecovery (120ms)
    end
    alt not unmounted after 260ms
        Scheduler->>RecoveryCallback: invoke reattachRecovery (260ms)
    end
    Window->>TerminalHook: unmount/cleanup
    TerminalHook->>Scheduler: clear stored burstTimeouts (clearTimeout) and reset array
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 I hopped back in when focus came near,
Three gentle nudges chased the stale veneer.
Immediate first, then ticks at two,
One more to make the canvas new—
Fresh frames hop in, the view is clear. ✨

🚥 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
Title check ✅ Passed The PR title accurately summarizes the main change: adding retry logic for terminal repainting after focus restoration.
Description check ✅ Passed The PR description covers the summary, rationale, testing approach, and related issues, though it is not formatted according to the template with formal sections.
Linked Issues check ✅ Passed The code changes directly implement the requirements from issue #3321: introduce burst-retry logic (immediate, ~120ms, ~260ms) in the focus/visibility recovery path and add a regression test.
Out of Scope Changes check ✅ Passed All changes are scoped to the terminal recovery path: adding burstTimeouts state, implementing scheduleRecoveryBurst(), updating recovery callsites, and cleaning up on unmount.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@greptile-apps

greptile-apps Bot commented Apr 10, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR addresses a lingering blank-terminal regression after focus/visibility restore by adding a short "recovery burst": after the immediate runReattachRecovery call, two additional deferred calls are scheduled at 120 ms and 260 ms, giving xterm time for the layout to settle before the final repaint.

  • useTerminalLifecycle.ts: adds a burstTimeouts array to reattachRecovery, introduces scheduleRecoveryBurst, updates handleVisibilityChange and handleWindowFocus to call it, and correctly clears burst timeouts in the unmount cleanup.
  • useTerminalLifecycle.test.ts: adds a four-test suite that models the throttle mechanism in isolation and validates the new burst behaviour. The makeScheduler helper's return-type annotation needs to include scheduleBurst (it's missing, causing TypeScript errors), and the stale "currently FAILS" comment should be updated now that the fix is in place.

Confidence Score: 4/5

Safe to merge after fixing the TypeScript return-type annotation in the test file; the production implementation change is small, well-isolated, and properly cleaned up.

The implementation change is minimal and self-contained — a burst of two extra setTimeout calls with proper cleanup. The only concrete issue is a TypeScript type error in the test helper (scheduleBurst not declared in the return type), which would fail typecheck CI. Once that one-liner annotation fix is in, the PR is ready.

useTerminalLifecycle.test.ts — return-type annotation for makeScheduler must include scheduleBurst.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts Adds scheduleRecoveryBurst which fires two follow-up scheduleReattachRecovery calls at 120 ms and 260 ms after focus/visibility restore; event handlers updated accordingly and cleanup path properly cancels outstanding burst timeouts on unmount.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts New regression tests for throttle/burst behaviour; the makeScheduler return-type annotation is missing scheduleBurst, producing TypeScript errors on both the return statement and the destructuring at the burst-test call site. The pre-fix "currently FAILS" comment is also stale.

Sequence Diagram

sequenceDiagram
    participant W as window / document
    participant H as handleWindowFocus / handleVisibilityChange
    participant B as scheduleRecoveryBurst
    participant S as scheduleReattachRecovery
    participant R as runReattachRecovery (xterm)

    W->>H: focus / visibilitychange
    H->>B: scheduleRecoveryBurst(forceResize)
    B->>S: immediate call
    S->>S: requestAnimationFrame
    S->>R: runReattachRecovery() [T=0]
    Note over R: clearTextureAtlas + fit + refresh

    B->>B: setTimeout 120ms
    B->>B: setTimeout 260ms

    Note over S,R: T = 120ms
    B-->>S: scheduleReattachRecovery()
    S->>S: requestAnimationFrame
    S->>R: runReattachRecovery() [T≈120ms]

    Note over S,R: T = 260ms
    B-->>S: scheduleReattachRecovery()
    S->>S: requestAnimationFrame
    S->>R: runReattachRecovery() [T≈260ms]
Loading

Comments Outside Diff (2)

  1. apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts, line 40-117 (link)

    P1 scheduleBurst missing from makeScheduler return type

    The declared return type of makeScheduler does not include scheduleBurst, but the function returns it and the burst test destructures it:

    // Line 111–116 – actual return includes scheduleBurst
    return {
        schedule: scheduleReattachRecovery,
        scheduleBurst: scheduleRecoveryBurst, // ← not in the declared return type
        flush: flushRafs,
        state: reattachRecovery,
    };

    TypeScript will raise two errors:

    1. Excess property – the object literal returned has scheduleBurst which does not exist in the annotated return type { schedule: ...; flush: ...; state: SchedulerState; }.
    2. Unknown property on destructuring – const { scheduleBurst, flush } = makeScheduler(...) (line 191) references a property that TypeScript doesn't know about.

    Fix: add scheduleBurst to the return type annotation.

  2. apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts, line 152-163 (link)

    P2 Stale "currently FAILS" comment

    The block comment above the "throttled recovery is retried after throttle window expires" test says "REPRODUCTION TEST — this test currently FAILS, demonstrating the bug." That was true before the fix was applied, but the scheduleReattachRecovery in useTerminalLifecycle.ts now includes the retry setTimeout, so this test passes. Leaving the comment as-is will confuse future readers into thinking the test is a known failure.

    Consider updating the comment to reflect the post-fix state, e.g.:

    /**
     * Regression test for issue #1873.
     *
     * Verifies that when scheduleReattachRecovery is called within the 120ms
     * throttle window it schedules a retry after the remaining throttle
     * duration, rather than silently dropping the request.
     */
    

Reviews (1): Last reviewed commit: "fix(desktop): retry terminal repaint aft..." | Re-trigger Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 2 files

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts (1)

40-44: ⚠️ Potential issue | 🟠 Major

Expose scheduleBurst in makeScheduler's return type.

The function returns an object with scheduleBurst at line 113, but the declared return type at lines 40-44 does not include it. This causes a TypeScript error when the test at line 191 destructures scheduleBurst.

🛠️ Proposed fix
 function makeScheduler(runRecovery: (forceResize: boolean) => void): {
 	schedule: (forceResize: boolean) => void;
+	scheduleBurst: (forceResize: boolean) => void;
 	flush: () => void;
 	state: SchedulerState;
 } {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts`
around lines 40 - 44, The declared return type of makeScheduler is missing the
scheduleBurst method, causing a TypeScript error when the test destructures
scheduleBurst; update the return type of makeScheduler to include scheduleBurst:
(forceResize: boolean, burstMs?: number) => void (matching the implementation)
so callers like the test at line ~191 can destructure scheduleBurst without type
errors, and ensure any SchedulerState reference remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts`:
- Around line 40-44: The declared return type of makeScheduler is missing the
scheduleBurst method, causing a TypeScript error when the test destructures
scheduleBurst; update the return type of makeScheduler to include scheduleBurst:
(forceResize: boolean, burstMs?: number) => void (matching the implementation)
so callers like the test at line ~191 can destructure scheduleBurst without type
errors, and ensure any SchedulerState reference remains unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 46ebaf8f-6d1d-43f7-b902-8a4056ea9e7d

📥 Commits

Reviewing files that changed from the base of the PR and between 23b53ed and 98a8470.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts

@surlymochan

Copy link
Copy Markdown
Author

Follow-up update pushed to this PR.

I addressed the concrete review items in the regression test:

  • added scheduleBurst to the makeScheduler return type
  • rewrote the stale "currently FAILS" block comment into a normal regression-test comment

Validation run locally:

  • bun test apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts
  • bunx biome check apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts

I kept the PR scope limited to the repaint/focus-restore bug in #3321. The other downstream fixes I found while validating the patched build are being split into separate upstream candidates so this PR stays reviewable.

@dorukardahan

Copy link
Copy Markdown

i have another repro in the same family here: #3504

on my mac, i can also get terminal content ghosting into the chat view, not only blank space inside the terminal. when i toggle fullscreen, it fixes the rendering right away.

that makes me think the repaint recovery path in this PR is very relevant for my case too.

i am closing my draft PR #3506 to avoid overlap. if this PR does not fully cover the chat ghosting and fullscreen case, i can send a smaller follow up later.

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 can still repaint into a stale visual state after focus/workspace restore

3 participants