fix(desktop): show main window when ready-to-show never fires#377
fix(desktop): show main window when ready-to-show never fires#377kzikit wants to merge 1 commit into
Conversation
On software-rendered systems (e.g. VMware SVGA where Chromium falls back to SwiftShader) the compositor never produces a first frame for a hidden window, so 'ready-to-show' never fires and the main window stays invisible forever while the app boots healthy underneath (DOM complete, no errors). Add a 2s fallback timer that shows the window anyway; on healthy systems ready-to-show fires first and startup stays flicker-free. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Review mode: initial
Findings
-
[Minor] Missing tests for the window-show fallback behavior. The PR author acknowledges this, citing that
createWindow()is not exported and is all side effects. While the change is small and the testability gap is real (refactoring would be out of proportion for this fix), the project's checklist requires tests for changes. Consider either exporting a testable helper or accepting this as a documented gap for a hotfix-level change. -
[Nit] Hardcoded 2-second fallback timeout (
apps/desktop/src/main/index.ts:119). On extremely constrained systems or under high load, 2s might still not be enough. Consider making the timeout a module-level constant (e.g.WINDOW_SHOW_FALLBACK_MS = 2000) with a brief inline comment explaining the choice, rather than a magic number.
Summary
This PR adds a 2s fallback timer that shows the main window when ready-to-show never fires — a bug affecting software-rendered systems (VMware/VirtualBox VMs where Chromium falls back to SwiftShader). The change is minimal (+21 lines, -1 line), well-documented, and includes a changeset. The existing ready-to-show path remains primary and is preserved with a clearTimeout guard. The closed event cleanup prevents timer leaks. No dependencies added, no security concerns, no architecture impact. A test is missing (acknowledged with a reasonable justification). The PR is directionally sound and safe to merge.
Testing
- Suggested: Manual testing on a VMware Fusion or VirtualBox VM to confirm the window now appears. Can also be tested by artificially delaying/faking
ready-to-showin a dedicated test build (e.g., overriding the event emitter in a preload script). - Not run: No automated test added (see Minor finding above).
Open-CoDesign Bot
Summary
On software-rendered systems — e.g. a VMware Fusion VM with the
VMware SVGA IIvirtual GPU, where Chromium falls back to SwiftShader (gpu_compositing: disabled_software) — the compositor never produces a first frame for a hidden window. The main window is created withshow: falseand only shown fromready-to-show(apps/desktop/src/main/index.ts), and that event is driven by the first frame. Result: chicken-and-egg — the window waits for a frame, the frame waits for a visible window. The app boots completely healthy underneath (single-instance lock acquired, snapshots DB up, renderer DOM fully loaded — verified via CDP) but the window stays invisible forever.--disable-gpuand--ozone-platform=waylanddo not help.This PR adds a 2s fallback timer that shows the window when
ready-to-shownever fires. On healthy systemsready-to-showfires well within 2s, cancels the timer, and startup stays flicker-free — no behavior change there.Diagnosis evidence: with the window hidden,
Page.captureScreenshotover CDP hangs indefinitely (no frames produced); with the window shown, the same call returns the fully rendered UI immediately.Type of change
Linked issue
None — happy to open one with the full diagnosis log if you prefer tracking it that way.
Checklist
pnpm lint && pnpm typecheck && pnpm testpasses locally (1366 tests, 111 files)createWindow()is not exported and is all side effects; covering a window-show timing fallback would require refactoring it for testability, which felt out of proportion for this fix. Glad to add one if you want the refactor.pnpm changeset) if user-visiblePRINCIPLES §5b
ready-to-showfires; fallback only triggers where the window previously never appeared at allshow: false+ready-to-showpath as the primary mechanism; the timer is a guarded escape hatch with the why documented inline🤖 Generated with Claude Code