While I was working on Vamprowser, Vampir seemed to have a problem with rendering things on top of native views, like the WebView. The most unorthodox shit was pulled, like screenshotting the page, hiding the native view, among other stuff.
GPUI isn’t made to interop with native views, far from it, it’s DESIGNED to be the view itself, which is why it’s been struggling with blending WebKit with itself for a while now
And so, a patch began as a PR on top of a 0.2.x checkout. It attempted to generalize native views, but omitted one big step: how to actually do it without lagging the application, consuming memory and handing over a shittier result.
It grew from “yeah lemme design this” into “Let me actually take a look”. The codebase was a MESS. But that’s what incentivized me to better look at my agents and how they’re doing. And so far, so good.
0.3 will include a GPUI-ce upgrade, platform-window fixes, code cleanup and optimization, as well as a way to deal with native resources without having to resort to unorthodox ways.
The Command Palette can now live outside of the main window in its own child
With how native views can be embedded within Vampir, people would want a system that would work with them. Albeit, in unorthodox ways.
With 0.3, you have the option to offload tooltips and other stuff, like the Palette to a child window behind a scrim (fancy word for that dim you see). This makes your Vampir app feel more within your desktop as opposed to it feeling like a self-contained application.
This took a lot of work, but what I am confident in saying is that the change is so broad and rigorously tested against so many edge case scenarios that Linux support is also guaranteed. With how X’s window positioning works, it works flawlessly.
With how Vampir works, you don’t really have to change so much of your codebase to enable it. You do have to worry about edge cases if you’re willing to uphold this separation, as some people wouldn’t like the change. That’s why this is an option, rather than a breaking change.
Speaking of GPUI-CE…
GPUI-CE has been upgraded to 0.3.0. Which means that many APIs that Vampir used are now offloaded into GPUI itself.
This also means that some glyphs started rendering incorrectly due to wrong API calls. Now, they’re doing better.
GPUI-CE also defines an anchored-popup primitive that basically defines what’s present within the Palette. However, it’s a Wayland-only feature.
NOTE: You must enable the patch yourself if you wish to use the window-child command bar. Dependency patches are not applied unless you apply yourself in cargo.toml under a [patch] clause;
[dependencies]
vampir = { path = "third-party/vampir", features = ["command-bar-window"] }
gpui = { package = "gpui-ce", version = "0.3" }
gpui_ce_platform = { version = "0.3", features = ["font-kit", "runtime_shaders", "x11"] }
[patch.crates-io]
gpui-ce = { path = "third-party/vampir/vendor/gpui-ce" }
gpui_ce_macos = { path = "third-party/vampir/vendor/gpui_ce_macos" }
gpui_ce_windows = { path = "third-party/vampir/vendor/gpui_ce_windows" }
gpui_ce_linux = { path = "third-party/vampir/vendor/gpui_ce_linux" }
Vampir’s GPUI-CE patch aims to make it universal. I’m planning to put a PR for it, but I doubt it would get accepted by the team because it’s too risky of a patch to delocalize it from Vampir. It’s tested within its own usecases, and with enough maturity, could be further improved.
The Vampir-Platform relation looks like this:
| Platform | Implementation |
|---|---|
| macOS | A child panel attached to the host window. |
| Windows | An owned popup tool window. |
| X11 | An override-redirect popup-menu window associated with its parent. |
| Wayland | The existing popup methods, with keyboard and dismissal fixes. Experimental, wontfix anything further. |
Overall, this shouldn’t pose much of an issue, but it is still an optional feature rather than a new paradigm imposed on users.
Windows is tended to as well.
On GPUI for Windows, embedded native widgets could fail and crash the app, and in certain cases also draw over the embedded view.
There’s also some platform issues that had to get fixed, like scaling issues and font rendering. That’s fixed as well.
Bug fixes galore!
Keyboard focus is fixed, so both the Vampir view and the native view get it, however this depends on whether it’s warranted. Of course, a GPUI input and a Native input will respond to keystrokes and combinations accordingly, so it’s up to you to take care of what gets what.
There’s also scaling issues, workspace issues, and more that have been fixed. App-crashing upstream bugs have also been resolved.
Oh yeah, menus have been fixed as well.
Menu actions in Vampir previously depended on string-based names for action lookup which is an extremely inefficient paradigm that could lead to errors.
Now, they’re ID-based, which means you could have the same string do two actions.
Another QoL change is that MenuIcon now accepts custom drawing, which means you can use your own glyphs. There’s also the added enabled field which gives users info into whether a function is usable in reusable menus.
An issue where rapidly clicking the menu while motionless has also been fixed.
Design issues have been fixed!
The footer had misplacement, the selection text contrast was horribly put. It’s simply a disastrous look that should’ve been fixed.
I did mention that Windows font rendering has been fixed. DirectWrite had something to do with it. I hate ClearType.
GPUI-CE 0.3.0 removed some glyphs that were crucial, including the tab/search close glyphs. They’re now rendered as vector paths. Some glyphs need to be replaced, one thing in particular is that you have to use .font(vampir::ui_font_face()) for a fallback. Of course, you don’t need that if your glyphs were custom to begin with, but it’s a good measure regardless.
Vampir’s colorways are also changed. Saturation now goes to 3 (300%) which means you can saturate your app more. But the way it worked is completely incompatible with moving up and beyond compatible values, so from 100% onward the algorithm raises some elements more, some elements less, and overall saturates the entire app to the point where you see the color, but it’s not deep-fried.
Optimizations galore! (The interesting part)
Oh boy. There’s a LOT to discuss.
First of all, Agents aren’t used to Rust. Despite it being a mature language, it’s not really agent-friendly. Not that I expect it to, for a language as serious as Rust, but a lot of the things that it gets wrong with Rust largely come from the fact that it’s used to taking shortcuts from other languages that have been optimized to all hell, like JS.
This isn’t JS. This is a statically typed object-oriented memory-strict language, that Rust often forgets to remember.
Now, I told my agents to scrub the code, and it found nothing. That’s what I’m worried about. Agents can’t tell their shit’s slow without an expert view. So I did a little lookup on Kagi and went into compiling something along the lines of “Rust Best Practices 101” to try and mitigate some shortcuts I was perfectly aware both Codex and Claude were pulling. And wouldn’t you believe it, applying it gave me a noticeable speedup.
Consult the charts below for an accurate representation of what exactly is sped up and how much
Search results
empty
matching
unmatched
empty
matching
unmatched
Exact values
| Operation | Before | After |
|---|---|---|
| Command: empty | 163.7004 | 18.5427 |
| Command: matching | 297.0309 | 193.3979 |
| Command: unmatched | 225.6826 | 190.3403 |
| Ranked: empty | 74.1197 | 70.5881 |
| Ranked: matching | 165.5926 | 134.6746 |
| Ranked: unmatched | 477.3808 | 321.3489 |
Menus and ownership
Exact values
| Operation | Before | After |
|---|---|---|
| Flat menu | 100 | 24.8551 |
| Nested menu | 100 | 22.0086 |
| Styled label | 100 | 16.0201 |
| Callback | 100 | 56.487 |
| Membership | 100 | 54.6151 |
Search highlighting
Exact values
| Operation | Before | After |
|---|---|---|
| Empty | 0.1651 | 0.0036 |
| Whole word | 0.526 | 0.3775 |
| Fuzzy | 0.5777 | 0.4915 |
| Failed | 0.5369 | 0.5363 |
| Overlap | 0.518 | 0.3157 |
| Unicode | 0.5271 | 0.3867 |
| Detail | 0.3868 | 0.2939 |
Allocations
empty
match
unmatched
empty
Exact values
| Operation | Before | After |
|---|---|---|
| Command: empty | 100 | 0.0497 |
| Command: match | 100 | 62.5872 |
| Ranked: unmatched | 100 | 28.6143 |
| Nested menu | 100 | 23.5294 |
| Styled label | 100 | 33.3333 |
| Highlight: empty | 100 | 0 |
Command cache
Exact values
| Operation | Before | After |
|---|---|---|
| Empty query | 12.2164 | 0.0105 |
| Matching query | 76.1341 | 0.0106 |
The biggest (non)issue that separates programmers from vibecoders is the fact that we learn. We grow. We pick design choices that obviously benefit (and not mess our flow up) and we use the tools at our disposal to speed things up without going the wrong direction.
agents.md
Speaking of Agents, I often saw (from my friends’ experience) that agents were simply just failing to create coherent Vampir apps.
Vampir is an Agent-first framework, where from 0.3 onwards it will get progressively better, to the point where Agents have guidelines in making applications from a single prompt, even on open-weights.
Before 0.3 it would struggle trying to make applications, and oftentimes copy from the Gallery, while omitting elements that could better the app. I have not seen a single Vampir app utilize the Radio button, or columns, and on horrible models it messes up the bottom bar, the top bar has unnecessary taglines.
0.3 includes “tutorials”, something your agents can base their work off of. It basically takes care of de-sloppifying your app, turning it into a clean, usable and fast application.
And?
That’s it!
Lots of things have to be added into Vampir but, like the previous release’s sentiment, It will only progressively get better, faster and feature-complete. Currently there’s no ideas on improving the framework, but GitHub is your friend. Issues and Pull Requests are always open, so you can go ahead and submit changes and keep the software going.
