titlebarTransparent: true in the render() call is
what does it, and it is not conditional on the platform: a native titlebar on
Windows would be a second titlebar on top of the first.
Dragging
On Windows, there is no caption to grab, so the app moves the window itself. The press records where the window and the cursor are, every move pushes the window by the cursor delta, and the release lets go.pointerEvents: 'auto'. An unfilled in-flowdivgets no GPUI hitbox at all, so without it the press never reaches the handler.- All three handlers on the same element. GPUIX arms the pointer capture on the press only when the element also listens for moves.
- A region that does not cover the buttons. The title text and the empty space drag; the controls do not.
src/window-drag.ts. It resolves the window handle from
the foreground window, verifies the process id, and moves the window with
SetWindowPos. A release that lands outside the window never reaches the
titlebar, so the move handler checks the button state and ends the drag
itself.
On macOS, the app retains the original AppKit mouse-down event before GPUI
delivers it to JavaScript. A titlebar press must match that event’s window and
coordinates while the left button is still held. Stale, repeated and unrelated
requests fail. Tracking is queued on the Cocoa main thread through
performWindowDragWithEvent:,
so JavaScript does not wait inside the native drag loop. AppKit owns subsequent
movement and release; native traffic lights and Spaces remain under OS control.
The same handler covers the conversation, sidebar, surface and settings titlebars.
On Linux, dragging the custom titlebar is deliberately disabled. GPUIX 0.11.0
does not expose the compositor move request/Wayland input serial needed to do this
correctly. Use your desktop’s Move Window menu action or configured shortcut.
General settings shows this limitation. Native chrome remains hidden on all
platforms; do not change titlebarTransparent to work around it.
The browser preview has no native window and does not drag the browser.
Development and verification
bun run dev builds the macOS Cocoa helper before starting the source window.
If running bun app.tsx directly on macOS, first run
bun scripts/build-window-macos.ts. The packaged app carries the same helper
beside its executable.
GPU hit tests cover all four drag regions and existing double-click behavior on
Windows/macOS. bun scripts/test-window-macos.ts compiles and runs the production
Objective-C bridge against controlled window/event objects, verifying event
identity, flipped coordinates, ownership, release races, stale requests and
asynchronous dispatch. It does not generate physical mouse input or claim to
measure window movement on a hosted macOS runner. The OS tracking call itself is
AppKit’s native implementation. Windows real-window integration remains in CI.
Maximize, restore, and the double click
The maximize button and a double click on the titlebar share one toggle.The native zoom only maximizes on Windows, it never restores. The restore
goes through
ShowWindow(SW_RESTORE), which also clears the zoomed state.
A plain window move restores the rectangle but leaves IsZoomed true, so
the next click would restore again instead of maximizing.<div /> elements have no onDoubleClick.
Why Windows does not use the OS move loop
The obvious way to drag a custom titlebar is to hand the drag to the OS modal move loop:GPUIX exposes no window move API. The native renderer has
minimizeWindow, zoomWindow, toggleFullscreen, activateWindow,
setWindowTitle and getWindowSize, and nothing to move a window with.
A startWindowDrag or setWindowPosition in packages/native would take
the FFI out of every app.