Skip to content

Commit 68976cb

Browse files
committed
docs: add technical report on Electron flickering under Hyprland
Comprehensive report documenting all attempted fixes, root cause hypotheses, and recommended next steps for another agent to investigate. Key findings: - Flickering is Electron-specific (Tauri and web Chromium work fine) - CSS fast profile, window timing, and GPU flags did not resolve it - center:true removal did not resolve it - Leading hypothesis: Hyprland entrance animations restarting due to rapid surface commits during Chromium initial load File: docs/electron-hyprland-flickering-report.md
1 parent 982c2c9 commit 68976cb

1 file changed

Lines changed: 221 additions & 0 deletions

File tree

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# Electron Flickering on Hyprland — Technical Report
2+
3+
**Date:** 2026-05-24
4+
**Platform:** Arch Linux + Hyprland (Wayland compositor)
5+
**App:** OpenLinear Electron desktop wrapper
6+
**Reporter:** kaizen
7+
8+
---
9+
10+
## Problem Summary
11+
12+
The Electron desktop app (`pnpm start:electron`) opens with severe visual flickering on Hyprland. The window appears to rapidly flash/resize during initial load, making the app unusable. This does **not** occur with the Tauri build on the same system, nor with the web app in Chromium.
13+
14+
---
15+
16+
## Environment
17+
18+
- **OS:** Arch Linux
19+
- **WM/Compositor:** Hyprland (Wayland)
20+
- **Electron:** v35.7.5 (system `/usr/bin/electron`)
21+
- **Node:** v24.12.0
22+
- **OpenLinear branch:** `dev`
23+
24+
---
25+
26+
## What Was Tried (Chronological)
27+
28+
### 1. CSS Flexbox Scroll Fix (chat-message-list.tsx)
29+
Added `min-h-0` to the chat message list scroll container. This fixed a WebKitGTK-specific flexbox scroll issue but is **irrelevant** to Electron (Electron uses Chromium, not WebKitGTK).
30+
31+
**Status:** Unrelated to flickering.
32+
33+
---
34+
35+
### 2. Electron Wrapper Creation
36+
Created `apps/desktop-electron/` as a minimal Electron main process wrapping the existing Next.js static export.
37+
38+
**Architecture:**
39+
- Main process: `src/main.ts` — creates BrowserWindow, spawns sidecar, handles IPC
40+
- Preload: `src/preload.ts` — exposes `window.electronAPI` via contextBridge
41+
- Sidecar: `src/sidecar.ts` — spawns `openlinear-sidecar` binary via Node child_process
42+
- Builder: `electron-builder.json` — targets AppImage + deb for Linux
43+
44+
**Frontend patches:** Runtime detection of Electron vs Tauri in:
45+
- `lib/api/client.ts` — sidecar URL resolution
46+
- `lib/api/auth.ts` — desktop login flow
47+
- `hooks/use-auth.tsx` — auth callback listener
48+
- `components/layout/sidebar.tsx` — window controls
49+
- `components/desktop/*.tsx` — native API calls
50+
51+
---
52+
53+
### 3. Initial Flickering Diagnosis — CSS Transitions
54+
55+
**Hypothesis:** The CSS "fast" render profile (disables backdrop-filter, transitions, animations) was only active for Tauri + Linux, not Electron + Linux.
56+
57+
**Fix:** Patched `app/layout.tsx` inline script to detect `window.electronAPI` and apply `data-openlinear-render-profile="fast"` for Electron on Linux.
58+
59+
**Result:** Flickering persisted. The CSS transitions were already disabled, but the window itself was still flashing.
60+
61+
---
62+
63+
### 4. Window Creation Timing
64+
65+
**Hypothesis:** `ready-to-show` event fires before the compositor has stabilized, causing a flash of partially-rendered content.
66+
67+
**Fixes tried:**
68+
- `paintWhenInitiallyHidden: false` — prevents painting until explicitly shown
69+
- `show: false` + `did-finish-load` (instead of `ready-to-show`) on Linux
70+
- 250ms `setTimeout` delay before `win.show()`
71+
- `backgroundColor: "#0a0a0a"` — prevents white flash
72+
73+
**Result:** Flickering persisted. The window shows but flashes during the first few seconds.
74+
75+
---
76+
77+
### 5. Hardware Acceleration & GPU
78+
79+
**Hypothesis:** GPU compositor conflicts with Hyprland's Wayland compositor.
80+
81+
**Fixes tried:**
82+
- `app.disableHardwareAcceleration()` — disabled GPU compositing entirely
83+
- `app.commandLine.appendSwitch("disable-gpu-vsync")`
84+
- `app.commandLine.appendSwitch("disable-software-rasterizer")`
85+
- `app.commandLine.appendSwitch("ozone-platform", "x11")` — force XWayland
86+
- `app.commandLine.appendSwitch("ozone-platform-hint", "x11")`
87+
- `process.env.ELECTRON_OZONE_PLATFORM_HINT = "x11"`
88+
89+
**Result:** Window went **completely blank** with error:
90+
```
91+
[ERROR:ui/base/x/x11_software_bitmap_presenter.cc:147] XGetWindowAttributes failed for window 1
92+
```
93+
94+
This indicates `disableHardwareAcceleration()` + XWayland force causes Chromium's software bitmap presenter to fail on Hyprland.
95+
96+
---
97+
98+
### 6. Window Properties
99+
100+
**Fixes tried:**
101+
- `transparent: false` — disables alpha blending
102+
- `hasShadow: false` — disables drop shadow
103+
- `frame: true` — native window frame instead of custom
104+
- `titleBarStyle: "default"` — standard title bar
105+
106+
**Result:** No improvement. Flickering persisted.
107+
108+
---
109+
110+
### 7. Positioning (Latest Attempt)
111+
112+
**Hypothesis:** `center: true` in BrowserWindow options causes Electron to create the window at (0,0) then reposition it to center. Hyprland animates this reposition with a slide effect, causing flicker.
113+
114+
**Fix:** Removed `center: true`. Calculated center position explicitly using `screen.getPrimaryDisplay().workAreaSize` and passed `x`/`y` to BrowserWindow.
115+
116+
**Result:** Flickering persisted.
117+
118+
---
119+
120+
## Current State (As of 2026-05-24 11:30 IST)
121+
122+
The Electron app:
123+
1. ✅ Compiles successfully (`tsc` passes)
124+
2. ✅ Next.js static export builds correctly
125+
3. ✅ Sidecar spawns and reports ready on ephemeral port
126+
4. ✅ Local HTTP server serves frontend files
127+
5. ✅ Window opens at correct position
128+
6.**Severe flickering during initial 2-3 seconds of window life**
129+
7. ✅ After flickering stops, app appears to function normally
130+
131+
---
132+
133+
## Leading Hypotheses (Unverified)
134+
135+
### Hypothesis A: Hyprland Window Animation Rules
136+
Hyprland applies entrance animations (slide/scale) to all new windows by default. Electron/Chromium's initial window surface may be resizing or repainting rapidly during load, causing Hyprland to restart the entrance animation repeatedly.
137+
138+
**Potential fix:** Add a Hyprland window rule to disable animations for OpenLinear:
139+
```conf
140+
# ~/.config/hypr/hyprland.conf
141+
windowrulev2 = noanim, class:^(OpenLinear)$
142+
windowrulev2 = noborder, class:^(OpenLinear)$
143+
windowrulev2 = noshadow, class:^(OpenLinear)$
144+
```
145+
146+
---
147+
148+
### Hypothesis B: Chromium Ozone/Wayland Backend Issues
149+
Electron 35 on Linux defaults to the Ozone platform abstraction. On Hyprland (a custom wlroots-based compositor), Chromium's Wayland backend may have unpatched bugs with surface commits during initial load.
150+
151+
**Potential fix:** Force native X11 backend (not XWayland) by setting environment variable before launching:
152+
```bash
153+
export ELECTRON_OZONE_PLATFORM_HINT=x11
154+
pnpm start:electron
155+
```
156+
Note: Earlier attempt with `app.commandLine.appendSwitch` failed because it was combined with `disableHardwareAcceleration()`. These should be tested **independently**.
157+
158+
---
159+
160+
### Hypothesis C: Next.js Hydration Mismatch
161+
The static export produces HTML that React then hydrates. During hydration, React may re-render the entire DOM tree, causing layout shifts that Electron/Chromium renders as visible flashes.
162+
163+
**Potential fix:** Add `suppressHydrationWarning` and ensure server/client HTML match exactly. Or disable SSR entirely for the Electron build.
164+
165+
---
166+
167+
### Hypothesis D: EventSource / SSE Connection Racing
168+
The frontend immediately tries to connect to `/api/events` via EventSource. If the sidecar isn't fully ready, rapid connection retries may cause UI updates (loading states) that manifest as flicker.
169+
170+
**Potential fix:** Delay EventSource connection until `sidecar:ready` event is confirmed.
171+
172+
---
173+
174+
## Recommended Next Steps for Investigating Agent
175+
176+
1. **Test Hyprland window rules** — Add `noanim` rule and verify if flickering stops. This is the fastest test.
177+
178+
2. **Test X11 backend without hardware accel disable** — Set `ELECTRON_OZONE_PLATFORM_HINT=x11` as an **environment variable** before launch (not via `appendSwitch`), and do NOT call `app.disableHardwareAcceleration()`. Test if this produces a stable window.
179+
180+
3. **Profile with `WAYLAND_DEBUG=1`** — Run with Wayland protocol debugging to see if there are excessive `wl_surface_commit` calls during flickering.
181+
182+
4. **Check `hyprctl clients` output** — Verify whether the Electron window is running under native Wayland or XWayland.
183+
184+
5. **Test with a minimal HTML file** — Create a minimal `BrowserWindow` loading `about:blank` or a simple static HTML file. If this also flickers, it's an Electron/Hyprland issue, not an OpenLinear app issue.
185+
186+
6. **Try `new BrowserWindow({ show: true })`** — Remove `show: false` entirely and let the window appear immediately. The `ready-to-show` pattern may be interacting poorly with Hyprland's animation system.
187+
188+
7. **Consider Tauri as primary Linux target** — If Electron cannot be made stable on Hyprland, the Tauri build (with the CSS scroll fixes already applied) may be the more pragmatic Linux choice despite WebKitGTK's limitations. The web app at openlinear.tech already works perfectly in Chromium.
189+
190+
---
191+
192+
## Files Involved
193+
194+
- `apps/desktop-electron/src/main.ts` — Main process, window creation
195+
- `apps/desktop-electron/src/preload.ts` — IPC bridge
196+
- `apps/desktop-electron/src/sidecar.ts` — Sidecar spawning
197+
- `apps/desktop-ui/app/layout.tsx` — CSS render profile detection
198+
- `apps/desktop-ui/app/globals.css` — Fast render profile CSS (disables transitions/animations)
199+
200+
---
201+
202+
## Build Commands
203+
204+
```bash
205+
# Development (loads from localhost:3000, no static build needed)
206+
pnpm dev:electron
207+
208+
# Production-like (builds static export + launches Electron)
209+
pnpm start:electron
210+
211+
# Build distributable
212+
pnpm build:electron:linux # AppImage + deb
213+
```
214+
215+
---
216+
217+
## Notes
218+
219+
- The Tauri desktop app (`pnpm --filter @openlinear/desktop tauri dev`) works without flickering on the same Hyprland setup, confirming this is Electron-specific.
220+
- The web app at `https://openlinear.tech` works perfectly in Chromium on the same system.
221+
- This strongly suggests the issue is in the **Electron ↔ Hyprland interaction**, not the OpenLinear frontend code itself.

0 commit comments

Comments
 (0)