A real browser inside a GPUI app, on two platforms out of three
Cydonia's right panel holds browser tabs: real pages, signed in where you are, that you browse and that agents can read and click through with Cydonia's browser tools. On macOS and Windows they work. On Linux the panel has no browser at all. This is how the tabs are built, and why the third platform got left out.
GPUI paints everything, except a web page
Cydonia is written in Rust on GPUI, the framework Zed is built on. GPUI draws the whole window itself, on the GPU: every rectangle, glyph and shadow is its own. A web page is the one thing it cannot draw. Rendering HTML means a browser engine, and the only engines worth embedding are the ones the operating system ships.
So the browser is a platform webview, through wry, the webview library behind Tauri. It is wrapped in bezel-browser, part of the bezel UI library Cydonia is built on:
| Platform | Page |
|---|---|
| macOS | WKWebView |
| Windows | WebView2 |
| Linux | None: the crate builds and paints nothing |
The page is a native view placed above GPUI's surface, not pixels GPUI paints. Every frame, GPUI lays out the tab and the page is moved to the rectangle it got.
Living above the paint
A native view on top has two consequences that shape everything else.
GPUI paints under it. A menu, a popover or a dialog that GPUI draws over the panel would be hidden behind the page. So when something covers the page, bezel takes a still of it, moves the real view away and paints the still in its place. When the cover goes, the view comes back to its rectangle and the still is freed. You see the page under the menu; it is a picture of the page.
GPUI's masks do not clip it. GPUI clips scrolled content with content masks, and a native view is outside them. The page has to be told exactly where it is, every frame, and taken off screen when its tab is not in front.
Parking a page turned out to need a place, not just a flag. Hidden alone, a WKWebView stays registered as a drop target over the rectangle it last had, and takes every drag that crosses it. So a parked page is moved to -20,000 points, where no drag goes.
Keyboard needs care too. A WKWebView takes edits such as copy and paste only as responder actions. Without an Edit menu wired to them, ⌘C in the page copies nothing, so on macOS bezel binds the edit keys and sends the page copy: and paste: itself.
Linux
On Linux, wry's webview is WebKitGTK, and WebKitGTK is driven by GTK's main loop. GPUI does not run GTK, and has its own event loop and its own windows. Wiring the two together took four pieces, and the backend had all of them:
- Run GTK without owning the loop. GTK is initialized once, forced onto X11, and its pending events are dispatched from GPUI's executor every 8 ms. Page input, painting and callbacks all wait for that pump.
- Translate the window. GPUI hands out an XCB handle for its window; wry takes an Xlib one. Same window id, wrapped differently.
- Reparent. wry builds the page in an X window of its own, which then has to be moved under GPUI's.
- Give up on Wayland. Under Wayland there is no X window to reparent into, so there was no page at all.
Even on X11 it never came together: in the Linux build, pages never showed. 0.1.20 left browser tabs out of the Linux build, and bezel 0.2.4 dropped the Linux backend entirely. On Linux the crate still compiles and the view paints nothing, so Cydonia builds the same way everywhere.
What would bring it back
The approach that works on macOS and Windows, a native view above the window, is the one Linux makes hardest: two toolkits, two event loops, and a display server that may not allow one window inside another. What Linux would probably need is a webview that renders offscreen into a texture GPUI can paint like anything else. That is a different engine and a different project. Until then, Linux users of Cydonia get everything except the browser tabs.
The webview is bezel-browser.