fix: show the window after React's first commit - #691
Merged
Ernxst merged 4 commits intoSep 25, 2026
Merged
Conversation
Ernxst
force-pushed
the
bb/task-600-first-frame-and-early-appkit-pump-thr_azhjng7mi7
branch
from
September 25, 2026 23:33
8b5308f to
a16a7ba
Compare
Takes two fork commits on top of the #599 pin: macOS displayLayer: frames request presentation instead of being throttled for inactive windows, and gpui_windows can show a window opened hidden without activating it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GPUI shows a `show: true` window while opening it, before React has committed, so its first present was an empty scene. On macOS and Windows, init() now opens the window hidden and the first tick() or tickIdle() shows it. render() commits before starting the frame loop, so the first frame the window presents contains the rendered tree. On macOS the reveal marks the layer for display, orders the window front and displays the layer if AppKit did not, so GPUI presents the committed tree with the transaction that shows the window, including for an unfocused window that opens covered. The default menus now wait for the shown window's second frame; the hidden window's own frame no longer counts as one of them. On Windows the reveal applies GPUI's pending placement, which keeps `focus: false` from activating the window. GPUI's Linux backends cannot open a window hidden, so Linux is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mount effect runs inside render()'s flushSync, before the frame loop's first tick. activateWindow() there dropped the pending reveal, and with it the deferred default-menu install, so a menus-less app lost its menu bar and Cmd+Q. It now performs the pending reveal itself, focused, which presents the committed tree and schedules the default menus, then activates as before. Adds testHasApplicationMenus() for the regression test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ernxst
force-pushed
the
bb/task-600-first-frame-and-early-appkit-pump-thr_azhjng7mi7
branch
from
September 25, 2026 23:41
a16a7ba to
4d04638
Compare
Ernxst
deleted the
bb/task-600-first-frame-and-early-appkit-pump-thr_azhjng7mi7
branch
September 25, 2026 23:48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Harness: Claude Code
Agent: none
Model: anthropic/claude-opus-5-5
Task statements
Fix GPUIX presents an empty first frame and pumps AppKit late after opening a window #600: pump AppKit straight after init, commit React before the window's first present so the first frame shows content, and install the default menus after the second frame; keep it correct for every entry path and on Windows and Linux. (Working-directory and workflow instructions omitted.)
Changes 1 and 3 are already on
mainfrom fix: pump AppKit sooner and install default menus after the first frames #608. Make the approved zed change so macOSdisplayLayer:frames request presentation, as a fork commit on top of macOS windows start their display link and tab queries late, adding about 25 ms before the first frames #599's pin. Fix the empty first frame on Windows as well if it has one, and say why Linux is unchanged. (Working-directory and workflow instructions omitted.)Closes #600.
GPUI shows a
show: truewindow while it opens it, before React has committed, so the first frame a GPU-IX window presented was an empty scene. #608 already landed the issue's other two changes: pumping AppKit on the first tick, and installing the default menus after the second frame. This PR covers the first frame.On macOS and Windows,
init()now opens the window hidden, and the firsttick()/tickIdle()shows it.render()commits before it starts the frame loop, so the tree is in place when the window appears:render() renderer.init() // opens the window hidden flushSync(root.render) // commits the tree startFrameLoop() tickIdle() + reveal the window // presents the committed tree pump AppKitdisplayLayer:frame request presentation. Without it, GPUI's inactive-window throttle often dropped that frame, and the window showed the scene it had presented while hidden until the display link drew. The default menus now wait for the shown window's second frame; before, the hidden window's own frame counted as the first.Window::new, before the root view existed. The window now opens hidden, and the reveal applies GPUI's pending placement through the newgpui_windows::show_window_opened_hidden(gpui: show a window opened hidden with its current frame zed#4), which keepsfocus: falsefrom activating it. A hidden window gets noWM_PAINT, so the first frame it presents is drawn after the commit.show: false, so there is no hidden state to reveal from.activateWindow()before the first tick, such as from a mount effect, performs the pending reveal itself, focused, so the window keeps its first frame and deferred default menus. A window opened withshow: falseis unchanged:activateWindow()still reveals it. A root that throws during the first render terminates before the window is ever shown.examples/first-frame.test.tsinitialises a renderer, commits a tree, pumps once and counts the frames presented in that pump. It covers a focused window,focus: false, and arequestAnimationFrameloop started from an effect:mainfocus: falsefocus: false+ rAFDefault menus still install: the menu bar has the app menu (Services, Hide, Hide Others, Show All, Quit with ⌘Q) and a Window menu, and a posted Cmd+Q key event quits the app.
Startup timing, for information:
examples/bench/hello-gpuix.tsx, 10 runs each alternating withmain(ed9832d3b0) on the same Mac with nothing else building, medians from launch. Present times come from a temporary log in GPUI'spresent():mainrequestAnimationFrameThe tree reaches the screen about 61 ms sooner. It comes with the present that shows the window, not with the display link. The second-
requestAnimationFramemarker is 3–16 ms later across two sessions of 10 runs each.Risk: a custom host that calls
createRenderer().init()now sees its window only at its firsttick()/tickIdle(). The README andskills/gpuixsay to commit before ticking. The Windows path is compiled and tested only by Windows CI. Reverting both commits restores the previous behaviour.🤖 Generated with Claude Code