anvilsign in

collin/browser-terminal-extension

main / CLAUDE.md

RenderedSource

CLAUDE.md

A tmux sidebar for Chrome and Firefox. Two halves that ship separately:

  • extension/ is the browser extension (named terminal). Plain files, no bundler: the browser loads the .js/.css/.html on disk as they are.
  • daemon/ is termbridge, a Rust CLI that serves a WebSocket on 127.0.0.1:7681, spawns a pty, and runs tmux inside it. tmux, not the daemon, is the persistence layer.

See README.md for protocol, pairing, TLS, and the systemd socket activation story. This file is the build and edit loop.

Build

./build.sh          # extension  -> dist/chrome and dist/firefox
./run.sh            # daemon: cargo build --release, then `termbridge serve`

build.sh copies files into dist/. The browser loads dist/chrome (or dist/firefox), never extension/. So an edit under extension/ has no effect until ./build.sh runs and the sidebar (or the extension) is reloaded. This is the most common reason a change "didn't work".

build.sh copies an explicit list of files. A new file under extension/ or extension/lib/ will not reach dist/ until it is added to that list. extension/mock.html is deliberately excluded: it renders the sidebar header in a plain tab for layout work, outside the extension.

.githooks/post-commit runs build.sh after any commit that touched extension/, so a committed change is at least in dist/. Enable it once per clone (hooks live outside the repo, so this is what points git at the tracked copy):

git config core.hooksPath .githooks

It does not reload the sidebar, and it does nothing for an uncommitted edit — ./build.sh by hand is still the loop while you are working.

Checks

npm test            # node --test over extension/lib/*.test.js
npm run check       # tsc over both jsconfig projects, noEmit
cd daemon && cargo test

npm run check is type checking only, never a build step. The extension files are classic scripts sharing one global scope (sidebar.js reaches Themes, Sanitize, tbPickElement with no imports), so nothing under extension/ may introduce import/export syntax. There are two tsconfig projects because there are two global scopes: jsconfig.json (sidebar document) and jsconfig.sw.json (service worker), both extending jsconfig.base.json.

Daemon tests use /bin/sh rather than tmux where they can, and skip themselves when tmux is missing; the tmux ones use a private socket so they never touch your real sessions.

Daemon edit loop

If the daemon is installed as a user service (termbridge install), a rebuilt binary is picked up on the next activation, but only once the running one exits:

cd daemon && cargo build --release
termbridge reload

Re-run termbridge install instead when the change affects what the unit encodes (port, idle timeout, pinned session, binary path).

Conventions

  • Both manifests (manifest.chrome.json, manifest.firefox.json) are hand maintained. A permission or file change usually has to land in both.
  • extension/lib/theme.js is the single source of truth for both palettes: the ui block becomes CSS custom properties, the xterm block goes to term.options.theme. Do not hardcode colors in sidebar.css; use the variables.
  • sidebar.css carries long comments explaining why a rule exists (the tab silhouette, the separators, the seam the active tab covers). Keep that style when editing it, and read the surrounding comment before changing a value.
  • extension/vendor/ is vendored xterm.js. Do not edit it.