Skip to content

fix: prevent macOS desktop unlock freeze after display sleep#745

Merged
boudra merged 2 commits into
getpaseo:mainfrom
yuruiz:fix/desktop-unlock-cascade-741
May 19, 2026
Merged

fix: prevent macOS desktop unlock freeze after display sleep#745
boudra merged 2 commits into
getpaseo:mainfrom
yuruiz:fix/desktop-unlock-cascade-741

Conversation

@yuruiz
Copy link
Copy Markdown
Contributor

@yuruiz yuruiz commented May 5, 2026

Summary

Fixes #741.

On macOS, locking the screen until the display sleeps can leave the Paseo
desktop window frozen after unlock — clicks and keypresses do not land —
even though the renderer and every process stay alive. It self-recovers
after a few minutes. Strongest repro: the Mac mini / external-display wake path.

Root cause: macOS display sleep can leave Chromium's GPU-process display
link — the vsync source that drives frame production — stuck on a stale
display. The compositor then stops producing frames, so the window looks
frozen while actually being alive. Restarting the GPU process makes Chromium
rebuild the display link, and frames resume.

Changes

setupDarwinCompositorWatchdog (packages/desktop/src/window/window-manager.ts)
replaces the previous setupDarwinPaintRefresh:

  • Polls the renderer for frame production every 2 s (requestAnimationFrame,
    bounded by a setTimeout so the probe resolves even when frame production
    has stopped).
  • After 3 consecutive stalled probes (~6 s) — only while the window is visible,
    not minimized, and the screen is unlocked — restarts the GPU process with
    SIGKILL; Chromium relaunches it automatically.
  • Bounded by a 60 s cooldown and a 3-attempt cap.

main.ts wires it in; window-manager.test.ts covers the
shouldRecoverFromFrameStall decision logic; docs/development.md documents
the gotcha. The previous setupDarwinPaintRefresh only nudged the window size /
invalidated the surface, which does not address the dead display link.

Note on the earlier revision

An earlier version of this PR attributed the freeze to a renderer-side
history-sync cascade and changed five app-layer files. Diagnostic builds showed
the freeze is frame-production death in the GPU process, not renderer
main-thread starvation — so the app-layer changes were dropped and this PR now
carries only the GPU-watchdog fix.

Verification

  • Built the desktop app from this commit and ran it until a real freeze
    occurred. The watchdog detected the frame-production stall, restarted the GPU
    process once (no second attempt needed), and the window recovered — versus
    the ~3-minute unmitigated freeze.
  • npx vitest run packages/desktop/src/window/window-manager.test.ts — 18 tests pass.
  • npm run typecheck (all workspaces), npm run lint, npm run format:check — pass.

Risk / compatibility

  • Desktop-only, gated to macOS (process.platform === "darwin"). No WebSocket
    or schema change.
  • The watchdog acts only on a sustained stall while the window is visible and
    unlocked; cooldown + attempt cap bound it.
  • The probe timer and all powerMonitor listeners are removed when the window closes.

Test plan

  • Mac mini + external display: lock until the display sleeps, unlock,
    interact — the window recovers within seconds instead of staying frozen.
  • MacBook Pro: normal lock/unlock — no regression.
  • Normal use across display-sleep cycles — confirm no spurious GPU restarts
    when there is no real freeze.

@boudra
Copy link
Copy Markdown
Collaborator

boudra commented May 5, 2026

hey @yuruiz apologies for the force push on main, i was going to rebase this but i see that you already merged. will look into your PRs in a moment.

@boudra boudra force-pushed the fix/desktop-unlock-cascade-741 branch from b97f281 to 590da87 Compare May 5, 2026 13:10
@boudra boudra force-pushed the main branch 2 times, most recently from 7ec394c to 5c90449 Compare May 8, 2026 11:48
@yuruiz yuruiz changed the title fix(app): defer history sync catch-up to user-visible panels fix: prevent macOS desktop unlock freeze after display sleep May 13, 2026
macOS display sleep can leave Chromium's GPU-process display link stuck
on a stale display, so the compositor stops producing frames and the
window looks frozen — unresponsive to clicks and keys — even though the
renderer stays alive.

setupDarwinCompositorWatchdog polls the renderer for frame production
and, on a sustained stall while the window is visible and unlocked,
restarts the GPU process so Chromium rebuilds the display link. This
replaces setupDarwinPaintRefresh, whose invalidate/resize nudges did
not address the dead display link.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yuruiz yuruiz force-pushed the fix/desktop-unlock-cascade-741 branch from 64e01d8 to 6a5c345 Compare May 18, 2026 13:09
@boudra boudra merged commit aaabadb into getpaseo:main May 19, 2026
13 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.

Desktop UI freeze on macOS unlock after extended display sleep (visibility resume cascade)

2 participants