perf(macos): unthrottled WebKit, parked launcher panel, paint-resize-syncro#451
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements a parked panel lifecycle and presentation-gated resizes on macOS to achieve a flicker-free rendering experience for the launcher and HUD, alongside idle-time scheduling and emoji font prewarming. The review feedback identifies two potential panic risks in macos.rs caused by calling .unwrap() on window.ns_window(), suggesting safer, graceful error-handling alternatives.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
Hey @AllDaGearNoIdea welcome back, Thanks for this — really impressive piece of work. The write-up alone (plus the new launcher-rendering-lifecycle.md doc) makes the "why" behind every one of these changes clear, which made reviewing it a lot easier than it could've been given how deep this goes into WebKit/AppKit internals. I tested the three changes locally against main and the difference is real: instant reopen even right after boot, no flash on grow into an extension view, and the rapid open→Escape→reopen bounce is genuinely gone. Nice touch pulling in the Raycast deep-dive for the presentation-update gating approach — the generation-counted supersession logic in particular is a clean way to handle the race. Merging this now. One thing worth keeping on our radar going forward: this leans on several private, undocumented Apple APIs (_setWindowOcclusionDetectionEnabled:, _setEnabled:forFeature:, _doAfterNextPresentationUpdate:, etc.). They're gated defensively so we degrade gracefully if Apple renames or removes one, but we should treat every macOS/Safari update as a "re-verify this still works" checkpoint rather than assuming it forever holds — silent regressions here would be easy to miss since they just fall back to pre-PR behavior rather than erroring loudly. Also worth watching over the next few weeks: since the launcher's webview and its background timers now stay fully active even while "hidden," it'd be good to sanity-check idle battery/CPU impact against main isn't materially worse. Appreciate you flagging the What's New flow issue separately rather than scope-creeping it into this PR — looking forward to that follow-up. Thanks again for the careful, well-tested work. I honestly missed your contributions. |
This is a V2 of the fluidity stuff I did before, but learning from Raycast's technical deep-dive
I used this as an opportunity to try out Claude Fable, which created
docs/explanation/launcher-rendering-lifecycle.mdand picked up and corrected some edge cases.Essentially, this aims to make the app feel as close as possible to a properly native macOS app, within the constraints of WebKit.
I've kept this dedicated and separate, so it accounts for the existing "what's new flow", but I believe this current implementation is wrong, and will put in a PR to correct that soon tldr the user shouldn't be essentially blocked from using the app after every update.
1. Keep WebKit unthrottled and at full cadence
WebKit throttles when a view isn't visible,
respondsToSelector-gated SPI, WKWebView + NSWindow spellings, logged fallbacks).requestIdleCallbackenabled via WebKit's feature-flag SPI, with a JS polyfill (src/lib/idle.ts) so nothing depends on the flag landing. What's New check, perf report, and a new emoji-font prewarm now run at idle instead of competing with first paint.PreferPageRenderingUpdatesNear60FPSEnabledoff) so rAF-driven UI tracks ProMotion displays at native cadence. Rendering stays demand-driven, so a static launcher draws no extra frames._doAfterNextPresentationUpdate), with a reveal-generation handshake for the HUD and watchdogs so nothing can wedge invisible.2. Panel lifecycle improvements
"Hidden" now means ordered in at alpha 0, mouse-transparent, never key, so the WebContent process stays in the visible activity state while hidden. Post-hide state resets composite invisibly, and every summon is an alpha flip of an already-fresh surface. A boot prewarm makes the first summon as instant as every later one. Focus returns to the previous app via an order-out/re-order-in pair (the one sequence that works for a non-activating panel of an Accessory app).
3. Resizes upon paint confirmation
Grows into extension views commit the NSWindow resize on exactly the webview paint that carries the new view: JS requests the resize at
$effecttime, Rust arms a presentation hook, JS confirms from a rAF in the swap's rendering update,and the hook commits resize + Show More bar in the same transaction as the new pixels. Generation-counted supersession and withdrawal make rapid open/Escape/open cycles stable (no 480→96→480 bounce), a parked-window shortcut keeps hidden transitions instant, and a watchdog guards the wedged-WebContent case. Window geometry only ever moves in sync with a request that still describes the current UI.
Note: Scoped to macOS, with existing behaviour as fallback for other platforms.
Testing
cargo test(2516),clippy --all-targets -D warnings,cargo doc -D warnings, vitest (launcher 2864, sdk 573) all green.idle.ts,emojiPrewarm.ts) have colocated tests.