Electron & embedded Chromium hosts
Vibe Annotations ships as a Chrome extension, but it isn't limited to Chrome. Any Chromium-based host that can side-load an unpacked extension can run the toolbar, including Electron. The same approach applies to other embedded Chromium runtimes (Edge WebView2, CEF) that implement the MV3 extension APIs; the examples below use Electron.
This is a development-only setup. You never ship the extension inside a packaged app.
How it works
Electron renders your app in its own bundled Chromium, which knows nothing about extensions you've installed in Chrome. So you load the unpacked extension directly into your Electron session during development. Once it's loaded, the content script auto-injects into your renderer via the static content_scripts match patterns, exactly as it does in a browser tab.
Two things make this painless on localhost:
- Injection on localhost happens through the static
content_scriptsmatch patterns, so the core annotate loop doesn't depend onchrome.scriptingorchrome.permissions(which embedded hosts don't implement). - Server calls run in the extension's background worker, not in your page. So you sidestep both CSP (
connect-src) and CORS entirely, because your renderer never talks to the server directly.
Requirements
- Electron recent enough to run MV3 background service workers. The extension-loading API lives at
ses.extensions.loadExtensionon Electron 35+, andses.loadExtensionon older versions. - Your renderer served from a localhost URL in dev (
http://localhost:<port>), or afile://page loaded withallowFileAccess. - The local server running:
npx vibe-annotations-server.
1. Build the unpacked extension
git clone https://github.com/RaphaelRegnier/vibe-annotations
cd vibe-annotations && pnpm install
cd packages/extension && pnpm build
The unpacked build lands in packages/extension/.output/chrome-mv3. That's the folder you point Electron at.
2. Load it into your Electron session
Load the extension before you create the window, so the content script is registered in time for the first navigation. If you load it after, you'll need to reload the window once for the toolbar to appear.
const { app, session, BrowserWindow } = require('electron')
function createWindow() {
const win = new BrowserWindow({ /* ...your options... */ })
win.loadURL('http://localhost:5173') // your renderer's dev URL
}
app.whenReady().then(async () => {
if (!app.isPackaged) {
const extPath = '/absolute/path/to/vibe-annotations/packages/extension/.output/chrome-mv3'
const ses = session.defaultSession
// Electron 35+
await ses.extensions.loadExtension(extPath, { allowFileAccess: true })
// Electron < 35: await ses.loadExtension(extPath, { allowFileAccess: true })
}
createWindow()
})
allowFileAccess: true is only needed if your renderer loads over file://; it's harmless otherwise.
3. Start the server and your app
npx vibe-annotations-server
Launch your Electron app in dev. The toolbar appears in the renderer, annotations sync to the server, and your AI coding agent reads them over MCP exactly as it does for a browser tab. See MCP Setup.
Known gaps
A few extension features depend on browser chrome that Electron doesn't provide. None of them block the core annotate to agent loop.
| Feature | Status in Electron | Why |
|---|---|---|
| Comment, design edits, variants, watch mode | Works | Content-script and background features, fully supported |
| Screenshots & image capture | Unavailable | chrome.tabs.captureVisibleTab isn't implemented in Electron. Leave screenshots off (the default) |
| Toolbar-icon toggle & keyboard shortcut | Unavailable | Embedded hosts have no extension toolbar icon and no chrome.commands. Use the in-page toolbar instead |
| "Enable on this site" for non-localhost | Unsupported | Relies on chrome.scripting / chrome.permissions, which Electron doesn't provide. localhost works out of the box |
Because there's no extension icon or keyboard shortcut to reopen a dismissed overlay, the toolbar's close (X) button is hidden in embedded hosts, so the toolbar stays in place and you can't strand yourself with no way back.
Non-Chrome host hygiene
The extension feature-detects Chrome-only APIs so a non-Chrome host never crashes the background worker on boot. chrome.commands and chrome.action.onClicked are optional-chained (both are undefined in Electron), and screenshot capture is off by default and fails gracefully if triggered. If you're building from source as above, these are already included. If you're loading an older packaged build and the worker throws on startup, rebuild from the current source.
Troubleshooting
- No toolbar appears. Confirm the extension loaded (
ses.extensions.getAllExtensions()should list it), and that your renderer URL matches a supported localhost pattern (localhost,127.0.0.1,0.0.0.0,*.local,*.test,*.localhost). - Worker crashes on boot. You're on a build without the host guards above. Rebuild from current source.
- Nothing syncs. The local server isn't running. Start it with
npx vibe-annotations-serverand check Troubleshooting.