- TypeScript 89%
- CSS 5%
- HTML 2%
- Rust 2%
- Shell <1%
- 3 more languages 1%
code-browser
A read-only code browser built for reviewing changes. It opens full files with the changes marked rather than a diff, and runs anything from the browsed repo only inside a sandbox. See docs/ARCHITECTURE.md.

Install #
There's no signed download: you build the app and sign it yourself, ad hoc, on the Mac that runs it. It needs mise for the toolchains and Podman for the sandbox image.
git clone https://tangled.org/mads.havmand.eu/code-browser
cd code-browser
mise install
make install
make install builds everything (the first run takes a while: it builds the
sandbox's kernel and root filesystem), packages dist/code-browser.app, signs
it with the entitlement that lets it start virtual machines, and copies it to
/Applications, replacing an installed copy (quit it first). make app stops
at dist/code-browser.app if you'd rather move it yourself. A bundle you
built isn't quarantined, so macOS opens it without a Gatekeeper prompt. To
update, git pull and make install again.
The installed app keeps its data in ~/Library/Application Support/code-browser.
Setup #
Toolchains are pinned with mise (Bun + Rust):
mise install
bun install # downloads Electron and ad-hoc signs it with the VZ entitlement
bun run build:native # Rust napi addon for Virtualization.framework sandboxes
bun run build:guest # sandbox kernel + initramfs (needs Podman), into guest/out/
bun run dev
Or run make, which does the bun install, native and guest steps as needed and
then starts bun run dev. The guest is only built when guest/out/ is missing;
rebuild it with make guest.
Bun handles package management, scripts and tests. The app runs on Electron's bundled Node, and Bun isn't used at runtime.
| Command | |
|---|---|
bun run dev |
Run the app with hot reload |
bun run build |
Production build into out/ |
make app |
Build and package dist/code-browser.app, ad-hoc signed (scripts/package-app.sh) |
make install |
make app, then copy it to /Applications |
bun run typecheck |
TypeScript, main + renderer |
bun test src |
Unit + git integration tests (incl. hostile-repo hardening) |
bun run test:store-node |
The review store's smoke tests under Electron's Node (node:sqlite, the backend the app uses) |
bun run build:native |
Build native/code-browser-vz.node |
bun run build:guest |
Build the guest kernel + initramfs in Podman |
bun run build:vzbench |
Build and sign the vzbench boot benchmark |
bun scripts/bench-startup.ts |
Time app start to ready review (after bun run build; see docs/BENCHMARKS.md) |
bun run icons |
Regenerate resources/icon.{png,icns} from resources/brand/ |
Using it #
-
Enter a local path or a git URL (
https://…,ssh://…,git@host:org/repo.git) and press Open. Remote repos are cloned bare and blobless into the app's data directory. -
The repository opens on its code at the current branch, with the README open. Comment on any line and the session becomes a draft review (an audit of that commit); browsing alone records nothing.
-
Compare… in the top bar: choose head (a branch, tag or SHA); base defaults to the merge-base with the default branch, like a PR. On a branch that's ahead of the default branch the button offers that comparison directly (Compare with main · N files). Changed files are badged in the tree. Open a file to see all of it, with changes marked in the gutter. Stop comparing goes back to the code. Reviews (⇧⌘R) lists the repository's reviews, with a count of replies waiting on you.
-
Start VM in the sandbox panel gives you a Linux VM with the head commit at
/work. The repo is read-only underneath, with a throwaway writable overlay on top. It's usually ready in a few ms from the warm pool. -
If the repo has a
.devcontainer, Devcontainer up builds and starts it inside that VM, and commands then run in the container.- Network access goes only through a host-side allow-list proxy, and every connection is logged in the panel.
- Click the
netcounter to see every host, add hosts to the allow-list (applies immediately), or switch mode: Allow-list, Monitor (allow everything, flag what the list would block) or Allow all. Private and loopback addresses stay blocked in every mode.
Open more windows with ⌘N, or launch again with a path
(…/code-browser.app/Contents/MacOS/Electron . ~/src/repo in dev). Launching again
reuses the running app. --profile=<name> runs a separate instance with its
own data.
Reviews are kept on your machine, never in the repository: one SQLite
store, reviews.sqlite in the app's data directory
(~/Library/Application Support/code-browser on macOS), with daily backups
in backups/ beside it (the newest seven are kept). A build that isn't
packaged (bun run dev) uses dev/reviews.sqlite there instead, so a branch
never migrates your real store; set CODE_BROWSER_REAL_STORE=1 to use the real
one in development. A submitted review can be exported to a GitHub pull
request, once, after you've seen what it will send. The app then fetches the
pull request's reviews, threads and replies and shows them beside yours; it
never writes there again. See docs/REVIEW-FORMAT.md
and docs/integrations/.
Settings (⌘, or type settings in the omnibar) edit
~/.config/code-browser/config.toml (config.<profile>.toml with --profile;
$XDG_CONFIG_HOME is respected). It's plain TOML you can also edit by hand: the
app keeps your comments and picks up edits as you save them. A repository can
set a few of the same keys in a committed .code-browser/config.toml, read from
its trusted branch; the page's Repository tab shows those, read-only.
Keys: ] / [ next/previous change · ⌥] / ⌥[ next/previous changed file · ⌘F search ·
⌘-click / F12 / gd go to definition · Shift+F12 / gr references (also in the right-click
menu) · F4 / Shift+F4 next/previous reference · hover for types and docs.
The sidebar's Symbols tab lists the review's changes by function, class or impl, marking each as added, removed, renamed, modified, formatting-only or comments-only. Outline lists the open file's definitions, and the viewer keeps the enclosing definitions in a header as you scroll. This comes from tree-sitter (WebAssembly, in a worker), so it needs no sandbox.
Code intelligence covers TypeScript, JavaScript, Python, Go and Rust. It comes from SCIP indexes built in the sandbox and cached per commit (instant, and they work without a VM), with language servers in the sandbox as the fallback. Dependencies are fetched in the sandbox first, so library types resolve, and go-to-definition into a dependency opens it read-only. With auto on (the default), opening a review starts the sandbox, brings up the devcontainer if there is one, then fetches dependencies and indexes.
Theming #
Colours, fonts and components come from the code-browser design system, whose
source is design-system/; the renderer imports it directly (@design-system).
After changing it, run bun run ds:build (it recompiles tokens.css from
tokens.json, refreshes the README's component index and builds the bundle), then
publish it to its Claude artifact with the push-design-system skill, for Design
canvases and other Claude sessions. The artifact is output: edits made there are
overwritten by the next publish. The theme follows the system appearance. App-only
tokens (the syntax colours) are in src/renderer/src/theme/app.css; layout and app
styles are in src/renderer/src/styles.css and use only tokens.
The logo, lockup and app icons are the design system's SVGs in
design-system/assets/Logos/. The dock, window and About-panel icons are rendered from
app-icon-dark.svg by bun run icons; commit the regenerated files.