Integrating Bolt With Convex: The Finale
If you haven't already, I'd recommend reading the first part of this series. To summarize where we are:
- Our goal is to connect bolt.new with convex.dev to make full stack apps that are runnable in your browser with the magic of WebContainers. The advantage of using Bolt over StackBlitz is that you get a little AI helper to make tweaks to your app - one that seemed to work quite well for a couple of toy apps I tried creating. I love having all this in the browser because it feels much easier to address an app with a URL rather than opening up a terminal and setting up all my tmux windows for it.
- We tried doing this with a CORS proxy, but this approach didn't work well since the actual
{app_id}.convex.cloud
URL cannot be overridden in the convex client today. I could have submitted a PR to the Convex repo, but the whole approach was beginning to feel icky. Additionally, I wanted a way to seamlessly get the files in and out of my local computer where I could make changes in the comfort of my local dev environment.
To check all the above boxes, I wanted a file transfer service that synced files over a Websocket so that it could be used across both a browser and a terminal (as opposed to using an SSH based pipe). The dev workflow would then be to run the file transfer service between Bolt and my laptop, run the Convex generator locally, and let the LLM go ham at editing Convex definition files while using the power of my local machine to run the Convex CLI. All the while the changes to the generated Convex files are being synced back up. Note that this requires building some notion of two-way file sync.
The UX I wanted for this file-syncing service was similar to wormhole (and ideally, the same security guarantees). The way wormhole works is pretty magical - you type:
wormhole send abcd.txt
And a code emerges: 5-arabian-nights
. This code is short, but secure, because it can only be used once for a given channel ID (the number at the beginning of the code). It also has the advantage that you can tell if you're being attacked since you'll see a number of wrong guesses across channels. Brain's PyCon talk was an incredible explanation of this protocol. There are a number of additional security properties it provides, like having an encrypted channel with the channel keys derived from the shortcode, but that knowledge is currently above my pay grade.
Anyway, what I most cared about for now was the UX. You needed to be able to send files back and forth easily, because otherwise there was no way I was using this setup. I realized that this didn't just have to be a file transfer service - it could be a more powerful primitive that provides two (or n) way communication between two clients given a shortcode.
I got to work designing QuasaRTC (get it, Quasar?). With some good ol' fashioned Rust coding and some Cursor assisted Node.JS client generation, I had a working primitive to build a file-syncing service on top of. The two-way sync is fairly simple, and simply sends the entire file over the wire when any change occurs. If there is a difference between the file sent over the wire and the one locally, the file is updated with the new contents. If there is a conflict between local and remote updates, we have a notion of a leader and a follower - the leaders changes always take precedence on conflicts (which should be rare for our workflow).
To test the fruits of my labor, I ran:
$ npx @quasartc/file-sync --url quasar-connect.cryingpotato.com --user-type leader --directory ./convex
info: 92-honeydew-date
And from the other end
$ npx @quasartc/file-sync --url quasar-connect.cryingpotato.com --user-type follower --directory ./convex --code 92-honeydew-date
And then.. it worked! The files were there, now all I had to do was to wait for things to build.. and I waited.. and waited..
Was it broken? I downloaded the files locally and ran, pnpm i && npm run dev
, and almost immediately saw the app open up. It dawned on me: WebContainers are just really slow. I didn't spend any time quantifying this statement, but anecdotaly I only got a build to complete once after many page refreshes. So there's that - you can integrate Bolt with Convex, but Webcontainers make the resulting dev experience too slow for useful work. There were also other annoyances with Bolt - since it's outside my existing neovim setup I have to use my (ugh..) mouse, there was a terminal bug where hitting the left arrow a lot caused the terminal to hang up, the terminal didn't have correct behavior for environment variables etc.
I jumped into this whole project because I was excited by the promise of async work. I love thinking about 10 things at once, dispatching a task to a separate project while I wait for one to build. All our dev tools today are so slow that you have time in between a pip install
or a docker build
where you can switch contexts, and while that's not always good for my stress levels or productivity for the task at hand, I do love the idea of telling a little worker to go ahead and try to finish up this idea in the background. I'll keep exploring this idea, but probably in different ways that don't use Bolt anymore. My subscription has been cancelled.