README.md

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.

code-browser with its own repository open: the file tree, this README rendered from Markdown inside the sandbox, and the review panel

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 #

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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 net counter 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.