Couchview is a local-first, mobile-optimized PWA for reviewing the combined HEAD → working tree diff of a Git repository. It keeps code nearly full width, makes file and hunk navigation fast, searches a tapped identifier across the project, and collects line comments into a Codex-ready correction prompt.
It shows staged, unstaged, partially staged, and untracked non-ignored changes in one stable queue. Staging a file does not remove it from that queue or mark it reviewed.
Couchview requires Git and Bun 1.3 or newer. If Bun is installed in its default user directory but is not on PATH, add it before using the package command:
export PATH="$HOME/.bun/bin:$PATH"
bun --versionPut the export in the shell profile if it should persist in new terminals.
From this Couchview checkout, install dependencies, build the production PWA, and link the couchview command:
bun install
bun run build
bun linkThen launch it from any directory inside the Git repository to review:
cd /absolute/path/to/project
couchviewThe repository and port can also be explicit:
couchview --repo /absolute/path/to/project --port 4173Open the project-specific URL printed by the command, such as http://127.0.0.1:4173/?repo=8f14e45fceea167a5a36dedd. Couchview resolves the containing repository root and binds to 127.0.0.1 by default, so it is accessible only from this computer. Use --host 0.0.0.0 to opt into access from other devices on the local network.
Run couchview inside another Git project while that endpoint is active to add it to the same server. The command prints whether the project was added, repeats its URL, and exits. Click the repository name in the app to switch projects; the selected repository is stored in ?repo=..., so browser history and separate tabs can keep independent projects open. Use another --port when intentionally running a different Couchview version or server instance.
couchview remains the shortest way to serve the current repository, while the
explicit serve command or --repo flag selects another repository. Bare paths
such as couchview ../project are rejected, keeping command completion distinct
from path completion. Conventional short options and inline values are supported:
couchview serve -r /absolute/path/to/project -H 127.0.0.1 -p 4173
couchview serve /absolute/path/to/project --port 4173
couchview --repo=/absolute/path/to/project --port=4173Use built-in help for the complete option, environment, and security reference:
couchview --help
couchview help serve
couchview restart --help
couchview --versionFor a guided launch, -i or --interactive prompts only for settings that were
not already supplied. It requires an attached terminal, so automated invocations
never wait for input:
couchview --interactive
couchview serve --interactive --repo /absolute/path/to/projectShell completion is generated on stdout by default. Fish can install and update
its completion file automatically in the standard per-user completion directory,
without editing config.fish:
couchview completion fish --installFish discovers that file automatically in new and existing shells. For dynamic
setup, or for zsh and bash, add the matching command to the shell profile to
complete commands, options, shell names, and repository directories only after
serve or a repository flag:
# zsh (~/.zshrc; compinit must be loaded before the generated script)
autoload -Uz compinit && compinit
source <(couchview completion zsh)
# bash (~/.bashrc)
source <(couchview completion bash)
# fish alternative (~/.config/fish/config.fish)
couchview completion fish | sourceRun couchview help completion to print these setup examples again.
To use Couchview from a phone on the same network, explicitly bind it to all IPv4 interfaces:
couchview --repo /absolute/path/to/project --host 0.0.0.0 --port 4173Startup prints every copyable address with LAN URLs first, for example:
Couchview URLs:
http://192.168.1.42:4173/?repo=8f14e45fceea167a5a36dedd
http://127.0.0.1:4173/?repo=8f14e45fceea167a5a36dedd
http://localhost:4173/?repo=8f14e45fceea167a5a36dedd
Repository: /absolute/path/to/project
Copy the 192.168... address into the phone browser. If it does not connect, confirm both devices are on the same Wi-Fi and allow incoming Bun connections in the computer firewall. The interface list is captured at startup, so restart Couchview after changing networks. A specific interface address can be used instead of 0.0.0.0, and COUCHVIEW_HOST provides the same setting through the environment.
LAN mode exposes repository diffs, staging controls, and detected package scripts to devices that can reach the computer. Use it only on a trusted network and stop the server when the review is finished. Plain http://<LAN-IP> works for reviewing, but mobile browsers do not treat it as a secure context: PWA installation, service workers, and direct clipboard access may be unavailable. Comment copying automatically falls back to selectable text.
Couchview provides one persistent tmux terminal per repository, rendered by
ghostty-web. Install tmux on the machine running Couchview and make it available
on PATH. A new session starts immediately in the repository with tmux's configured
default shell. Couchview loads the host user's normal XDG or ~/.tmux.conf first,
then enforces persistence, mouse, focus, and true-color settings required by the
browser terminal. Review hides the mounted terminal without ending it; reconnects,
reloads, and Couchview restarts reattach to the same tmux session. End session
kills that session and every program running in it.
Appearance is browser-owned and never reads the host Ghostty configuration. Open
Settings to tune the diff and terminal independently. Both can use bundled
Iosevka or the browser's system monospace stack. Diff controls cover font size,
line height, and letter spacing; terminal controls cover font size plus pixel-based
cell height and width adjustments (the terminal grid's row and column spacing).
Cell width can be tuned from −5px to +5px. Settings has its own /settings route,
while preserving the selected repository when returning to Review.
Preferences are stored in the current browser. The fixed Catppuccin Mocha terminal
palette and Safe Mode defaults are bundled, while terminal renderer and WASM assets
load only after the terminal is opened and are not part of the PWA precache.
With the terminal focused, Cmd++/Cmd+- on Apple devices or Ctrl++/Ctrl+-
on Windows and Linux change only its font size; Cmd+0 or Ctrl+0 restores the
configured size. Couchview prevents browser zoom, re-fits tmux, and keeps
the temporary size across reconnects and Review handoffs. Reloading resets it.
Terminal access is enabled automatically only when the bind address and every allowed origin are loopback. Disable it explicitly when desired:
couchview --disable-terminal
# or
COUCHVIEW_TERMINAL=0 couchviewLAN, tunnel, and reverse-proxy origins require an explicit opt-in:
couchview --host 0.0.0.0 --enable-terminal
# or
COUCHVIEW_TERMINAL=1 couchview --host 0.0.0.0This opt-in is security-sensitive: browser keystrokes control tmux and its programs with the same operating-system permissions as Couchview. Use it only on trusted networks or behind strong authentication such as Cloudflare Access. Couchview's origin and CSRF checks are not remote-user authentication.
Each repository gets one tmux session and one controlling browser tab. Another tab must confirm before taking control. Switching back to Review, closing the page, or restarting Couchview detaches the browser while tmux keeps running. End session warns once before terminating every program in the session, including unsaved work. Forgetting a repository uses the same warning.
The terminal runs on the Couchview host. If Couchview itself runs on a remote machine, tmux, its shell, and the repository are remote automatically. The authenticated WebSocket always provides browser attachment, authorization, signaling, and reconnection.
Terminal traffic can opportunistically move from that WebSocket to an ordered, reliable WebRTC DataChannel. This is a separate explicit opt-in and requires terminal access itself to be enabled:
couchview --enable-terminal --enable-terminal-p2p
# or
COUCHVIEW_TERMINAL=1 COUCHVIEW_TERMINAL_P2P=1 couchviewUse --disable-terminal-p2p or COUCHVIEW_TERMINAL_P2P=0 to force WebSocket
transport. P2P is disabled by default, including when terminal access is enabled
automatically on loopback. The toolbar reports Finding direct path, Direct
P2P, WebSocket, or WebSocket fallback. When ICE cannot establish a
direct route, the existing terminal stays attached over WebSocket. If an active
DataChannel is lost or exceeds its bounded backpressure buffer, Couchview
immediately reattaches over WebSocket without ending tmux; use Retry P2P to
make another direct-path attempt.
The default ICE discovery server is stun:stun.cloudflare.com:3478. Override it
with one to four comma-separated stun: URLs:
COUCHVIEW_TERMINAL_STUN=stun:stun.example.net:3478,stun:backup.example.net couchview \
--enable-terminal --enable-terminal-p2pThere is deliberately no TURN relay. Networks that block UDP, symmetric NATs, and restrictive firewalls can therefore prevent a direct path; WebSocket remains the fallback. The authenticated control WebSocket stays open during P2P, and the browser renews a same-origin authorization lease through the protected HTTP API. Closing that controller, taking over from another tab, ending the session, forgetting the repository, or restarting Couchview also tears down WebRTC.
P2P changes the privacy boundary: ICE can reveal peer IP addresses to the authorized browser and Couchview host, and terminal payloads on a direct path no longer traverse the configured reverse proxy or tunnel. That origin still carries signaling, lease renewal, and WebSocket fallback. Enable P2P only when direct peer exposure is acceptable.
Couchview can be published through Cloudflare Tunnel and protected by Cloudflare Access. The origin must stay bound to loopback, and Access must be configured before the tunnel hostname is published. A tunnel alone provides encrypted connectivity but does not authenticate Couchview users.
Use the following account-agnostic process to configure a deployment:
- Add the domain to Cloudflare and create a Zero Trust organization if one does not already exist.
- In Zero Trust > Integrations > Identity providers, configure the team's identity provider. Email one-time PIN is sufficient for a small private deployment.
- In Zero Trust > Access controls > Applications, create a Self-hosted
application for the complete hostname, such as
couchview.example.com. Create an Allow policy containing only the exact email addresses or identity-provider groups that should have access. Do not use Everyone or an unrestricted One-time PIN login-method rule. Select only the intended identity provider, use a suitably short session such as 24 hours, and enable the HttpOnly cookie option. Leave Binding Cookie disabled for browser and PWA access unless every client is known to preserve that additional cookie; a missing binding cookie makes Cloudflare reject an otherwise valid Access session. UseLaxorNonerather thanStrictfor the cookie SameSite setting to avoid authentication redirect loops. - In Networking > Tunnels, create a remotely managed tunnel and install the
displayed
cloudflaredcommand on the same computer as Couchview. Treat the tunnel token in that command as a secret and never place it in the repository. - Add a Published application route from the same hostname to
http://localhost:4173. The dashboard creates the proxied tunnel DNS record; confirm that the route ends with a catch-all HTTP 404 rule.
Then choose how to run the Couchview origin. Both options keep it bound to
loopback; cloudflared is a separate process and may remain connected while
Couchview is stopped.
This is recommended for occasional use because the logs remain visible and
Ctrl-C stops the server:
COUCHVIEW_ALLOWED_ORIGINS=https://couchview.example.com \
couchview \
--host 127.0.0.1 \
--port 4173 \
--repo /absolute/path/to/projectMultiple exact origins may be comma-separated. Do not use wildcards.
For an unattended server that starts after the macOS user logs in, create
~/Library/LaunchAgents/dev.couchview.server.plist. Replace every example
username and absolute path before loading it:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>dev.couchview.server</string>
<key>ProgramArguments</key>
<array>
<string>/Users/you/.bun/bin/bun</string>
<string>run</string>
<string>/absolute/path/to/couchview/src/server/cli.ts</string>
<string>--repo</string>
<string>/absolute/path/to/project</string>
<string>--host</string>
<string>127.0.0.1</string>
<string>--port</string>
<string>4173</string>
</array>
<key>WorkingDirectory</key>
<string>/absolute/path/to/couchview</string>
<key>EnvironmentVariables</key>
<dict>
<key>COUCHVIEW_ALLOWED_ORIGINS</key>
<string>https://couchview.example.com</string>
<key>PATH</key>
<string>/Users/you/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>StandardOutPath</key>
<string>/Users/you/Library/Logs/couchview.out.log</string>
<key>StandardErrorPath</key>
<string>/Users/you/Library/Logs/couchview.err.log</string>
</dict>
</plist>Validate and load the agent:
plutil -lint "$HOME/Library/LaunchAgents/dev.couchview.server.plist"
launchctl bootstrap \
"gui/$(id -u)" \
"$HOME/Library/LaunchAgents/dev.couchview.server.plist"
launchctl print "gui/$(id -u)/dev.couchview.server"It has no attached terminal. Follow its logs with:
tail -f \
"$HOME/Library/Logs/couchview.out.log" \
"$HOME/Library/Logs/couchview.err.log"Stop it and prevent automatic restarts with:
launchctl bootout \
"gui/$(id -u)" \
"$HOME/Library/LaunchAgents/dev.couchview.server.plist"Use a real property-list file for this mode rather than launchctl submit.
The agent runs as the logged-in user and starts after login, not at the macOS
login screen. Keep the Cloudflare tunnel token out of this file and the
repository.
After starting either option, visit the external URL in a private browser window. Cloudflare should request authentication before any Couchview response is visible. After authenticating, verify that a diff loads and that a write such as marking a file reviewed succeeds. Stop Couchview when remote review is not needed; the tunnel may remain connected and will return an unavailable-origin response.
No inbound router port or firewall rule is required because cloudflared opens
outbound connections to Cloudflare. If the token is disclosed, refresh it in the
tunnel's Overview page and reinstall the connector service with the replacement
token.
Couchview can make a repository on the host Mac available to a normal remote IDE
without publishing TCP port 22. Zed invokes the system OpenSSH client, OpenSSH invokes
Couchview through a generated ProxyCommand, and Couchview carries the complete SSH
byte stream over the configured Couchview origin. That origin can be a LAN address, a
VPS, any HTTP/WebSocket-capable reverse proxy or tunnel, or a compatible private relay.
The connection starts on a WebSocket and, when explicitly enabled and reachable, moves to an
ordered, reliable WebRTC DataChannel. If WebRTC cannot connect, WebSocket remains the
automatic fallback.
If a direct path fails after the handoff, Couchview closes that SSH transport so OpenSSH or Zed can reconnect cleanly instead of risking duplicated or reordered SSH bytes. A failed negotiation before handoff simply continues on WebSocket.
Pairing, device credentials, short-lived tickets, signaling, authorization leases,
SSH forwarding, and WebRTC are Couchview protocols. An origin-access provider only
adds headers required by an optional gateway in front of those protocols. none is
built in for direct LAN/VPS origins and transparent tunnels. cloudflare-access is a
separate adapter that obtains a header with cloudflared; it is not used by the bridge
core and never carries direct WebRTC traffic.
Providers have stable lowercase IDs and implement the small
RemoteBridgeOriginAccessProvider interface in
src/server/remoteBridgeOriginAccess.ts. HTTP and WebSocket creation are also injected
through RemoteBridgeClientRuntime, so a compatible relay can replace those connectors
without changing the SSH/WebRTC byte pump. Couchview uses
X-Couchview-Bridge-Token for its own device credential, leaving the standard
Authorization header available to OAuth, another tunnel, or a relay adapter.
For compatibility, the client also sends the device credential as a bearer token when
the selected gateway does not use Authorization.
An authenticated browser session is not copied into the Air's CLI. Gateway cookies are
normally browser-scoped and HttpOnly, and WebRTC still needs authenticated signaling,
ticket, and lease requests before a direct channel can carry SSH. To avoid cloudflared
on the Air, generate the pairing from a LAN or VPN origin, use a transparent origin that
does not require an extra gateway login, or install a different origin-access adapter.
A transparent tunnel or relay requires no Couchview-specific adapter when it forwards:
- POST requests for pairing claim, host-wide ticket issue, and lease renewal;
- WebSocket upgrades for
/api/remote-bridge/socket; Host,Upgrade, andSec-WebSocket-Protocolwithout rewriting their meaning.
After WebRTC activates, the origin carries only control traffic and lease renewal. It also remains the fallback data path when ICE cannot connect. A relay with a completely different, non-HTTP signaling protocol needs a connector implementing the same control operations, but it does not require changes to pairing, SSH, or WebRTC framing.
On the Mac mini:
-
Enable System Settings > General > Sharing > Remote Login for the intended macOS user. Configure normal SSH key or password authentication; Couchview does not bypass OpenSSH authentication or host-key verification.
-
Choose how the Air reaches Couchview.
On a trusted LAN, serve directly and open the displayed Mini address from the Air:
couchview serve /absolute/path/to/project \ --host 0.0.0.0 \ --enable-remote-bridge \ --enable-remote-bridge-p2p
This path uses the
noneprovider and requires nocloudflared. Do not expose this LAN mode to an untrusted network: Couchview's origin and CSRF checks are not remote user authentication.With Tailscale, install and sign in to Tailscale on both Macs, then find the Mini's stable Tailscale IPv4 address:
tailscale ip -4
Bind Couchview only to that address, replacing the example IP with the command's output:
couchview serve /absolute/path/to/project \ --host 100.101.102.103 \ --enable-remote-bridge
Ensure the tailnet policy allows the Air to reach TCP port
4173on the Mini. On the Air, openhttp://100.101.102.103:4173, choose Native IDE, and run the generated command. It will resemble:couchview bridge pair \ --url 'http://100.101.102.103:4173' \ --code '<one-use-code>'
Tailscale is the private network boundary, so this uses the
noneprovider and requires neithercloudflarednor a manual--origin-accessflag on the Air. Traffic stays on the origin WebSocket over Tailscale by default. Add--enable-remote-bridge-p2pon the Mini only if you also want WebRTC and accept its peer-address disclosure and possible alternate direct path.A Tailscale MagicDNS name can replace the IP in the browser and pairing URL. When doing so, add its exact origin, including port, to
COUCHVIEW_ALLOWED_ORIGINS, for examplehttp://mini-name:4173. See Tailscale's guides for connecting to devices and MagicDNS.For a VPS, reverse proxy, or tunnel, configure its exact public origin and keep a local Couchview origin on loopback when the connector runs on the same host:
COUCHVIEW_ALLOWED_ORIGINS=https://review.example.com \ couchview serve /absolute/path/to/project \ --host 127.0.0.1 \ --enable-remote-bridge \ --enable-remote-bridge-p2p
A transparent tunnel needs no provider.
auto, the default, recognizes Cloudflare Access requests and otherwise generates anonepairing. To select another installed adapter explicitly, add--remote-bridge-origin-access private-relayor setCOUCHVIEW_REMOTE_BRIDGE_ORIGIN_ACCESS=private-relay. -
For a LaunchAgent, add the bridge flags as separate
ProgramArguments. The equivalent environment values areCOUCHVIEW_REMOTE_BRIDGE=1andCOUCHVIEW_REMOTE_BRIDGE_P2P=1. WebRTC is optional; omit its flag to use only the origin WebSocket. A loopback SSH daemon on a nonstandard port can be selected withCOUCHVIEW_REMOTE_BRIDGE_PORT. The target host is deliberately fixed to a numeric loopback address.
Then pair the MacBook Air:
-
Install or link this Couchview CLI on the Air. Install
cloudflaredonly when the generated command selects thecloudflare-accessprovider. LAN, VPS, transparent tunnel, and other provider paths do not call it. -
Open any repository in Couchview, choose Native IDE, enter a device name, and generate a one-use command. Pairing is host-wide: the same device can open every repository registered in that Couchview server.
-
Run that command once in Terminal on the Air. The selected origin-access adapter supplies any gateway headers, then Couchview stores a private device credential, creates a managed OpenSSH host alias, and prints the Zed URL. With Cloudflare Access,
cloudflaredis used only for login and token retrieval; IDE traffic still uses Couchview's WebRTC path or the origin WebSocket fallback. -
After the device appears, select any repository and reopen Native IDE. Couchview shows separate copyable Zed, Codex, terminal, and Claude Code Remote Control commands containing that repository's remote path. Zed performs its normal remote-server installation through OpenSSH on first connection. The Zed command follows its remote-development SSH URL format:
zed 'ssh://<managed-ssh-host>/absolute/path/to/registered/project'
Pairing authorizes the Couchview transport but does not replace macOS SSH authentication. Before the first remote launch, connect to the printed SSH host once to verify its host key and login:
ssh <couchview-ssh-host>For password-free launches, install the Air's public key on the Mini first, for
example with ssh-copy-id <couchview-ssh-host>, and confirm that the plain ssh
command succeeds.
To open an ordinary remote terminal in the selected repository, run:
couchview bridge terminal \
--profile <managed-ssh-host> \
--repo /absolute/path/to/registered/projectThis starts the remote account's login shell without creating or managing a persistent
session. From there the user can start tmux, Claude Code, Neovim, or any other terminal
tool. Closing the shell closes the SSH connection.
To start Claude Code Remote Control in the repository as a single command, run:
couchview bridge claude \
--profile <managed-ssh-host> \
--repo /absolute/path/to/registered/projectThe Mini must have Claude Code installed and authenticated with a Claude.ai account.
The command runs claude remote-control remotely and leaves its status, session URL,
and QR code visible in the Air's terminal. After startup, browser and mobile control
traffic uses Anthropic's TLS service; Couchview carries the SSH launcher, not the Claude
conversation. The remote process remains attached to that SSH session. To keep it alive
after closing the Air's terminal, first open couchview bridge terminal, start tmux,
and run claude remote-control inside it. Claude arguments can follow --, for example:
couchview bridge claude \
--profile couchview-project-name-12345678 \
--repo '/Users/mini/Code/Another Project' -- \
--name 'Another Project'To keep the Codex terminal UI on the Air while Codex reads files and runs commands on the Mini, use:
couchview bridge codex \
--profile <managed-ssh-host> \
--repo /absolute/path/to/registered/projectWhen only one bridge profile is stored, --profile can be omitted from the terminal,
Claude, and Codex launchers. Omitting --repo uses the repository from which the pairing
was originally created. The machine-local profile ID is also accepted, but the managed
SSH alias is easier to match with the host used for ssh-copy-id. Arguments after --
are forwarded to the Air's Codex CLI, for example:
couchview bridge codex \
--profile couchview-project-name-12345678 \
--repo '/Users/mini/Code/Another Project' -- \
--model gpt-5.4The launcher selects private loopback ports, starts
codex app-server --listen ws://127.0.0.1:<port> in the selected repository through the
Mini's login shell, waits for its readiness endpoint through an OpenSSH local forward,
and then runs codex --remote ws://127.0.0.1:<local-port> on the Air. The app-server
and forward stop when the local Codex TUI exits. The Air and Mini both need a compatible
Codex CLI, and the Mini must already be authenticated with codex login. No Codex TCP
port is exposed beyond loopback. Because the forward is an ordinary SSH channel, it
automatically uses Couchview's WebRTC path when available and its protected WebSocket
fallback otherwise.
The automation keeps Couchview profiles in
${XDG_CONFIG_HOME:-$HOME/.config}/couchview/remote-bridges.json, writes managed SSH
hosts to ~/.ssh/couchview_config, and adds one Include ~/.ssh/couchview_config line
to ~/.ssh/config. Directories and credential files use user-only permissions. The
generated couchview bridge proxy command is an OpenSSH transport helper and should
not be run manually.
Pairing codes are single-use and expire after five minutes. Persistent host-wide device secrets are stored only as hashes on the Mini, transport tickets expire after 30 seconds, and active connections require a short renewable authorization lease. Revoking a device from any repository's Native IDE removes its access to every registered repository and disconnects its current bridge. The bridge cannot select another TCP destination and never stores SSH private keys.
As with terminal P2P, WebRTC can expose the Mini and Air's peer addresses to each other, has no TURN relay, and may be unavailable behind symmetric NAT or restrictive UDP firewalls. The configured origin and optional access provider continue to protect signaling, lease renewal, and WebSocket fallback; SSH provides end-to-end host authentication and encryption on both transports.
To run without linking the command:
bun run build
bun run start -- --repo /absolute/path/to/project --port 4173After changing Couchview itself, open the repository picker and choose Rebuild & restart
Couchview. The running production server executes bun run build in its own Couchview
checkout, builds into a temporary directory so a failed build cannot replace the current
UI, then replaces its server worker on the same host and port and reloads the current
review. The foreground supervisor remains attached to the launching terminal, including
across repeated restarts. couchview restart triggers the same action from another shell.
This action is intentionally unavailable in development mode, where Vite already reloads
client changes, and when STATIC_DIR points at a custom build.
For application development, run the Bun API and Vite UI together:
bun run dev -- --repo /absolute/path/to/projectDevelopment also binds both processes to 127.0.0.1 by default. The UI proxies /api, including server-sent events, to the loopback API endpoint at http://127.0.0.1:3001. Pass --host 0.0.0.0 to opt into phone access and print the phone-accessible frontend URLs. PORT can change the development API port and COUCHVIEW_WEB_PORT can change the Vite port.
- Click the repository name to open the project picker. It shows canonical paths, marks the current project, and keeps missing projects visible as unavailable. Forget requires confirmation and permanently removes that project’s saved reviews and comments.
- Open the file drawer to jump directly to a file or filter the queue by All, Unreviewed, Reviewed, or Staged. The persistent arrow controls visit the previous or next file;
[and]do the same from the keyboard. - Use the hunk up/down controls, or
KandJ, to jump between changes in the current file. - Tap an identifier in a diff to run a literal, case-sensitive project search. Results are separated into Current file and Other files. Opening a result shows a read-only source window with a one-tap return to the active diff.
- Line numbers are hidden by default so the code gets the widest possible viewport; tap
123to reveal them. Use the adjacent line-wrap control to switch long lines between horizontal scrolling and wrapping. Both display preferences are stored in the browser. Tap a number to select a line, then another number in the same hunk to extend the range. For a replacement, select a deletion and an addition to retain exact old and new ranges in one mixed comment. - Save any number of independent comments. The comments tray can jump to, edit, or delete them. Copy exports every current, non-stale comment as Markdown with repository-relative paths, exact old/new ranges, excerpts, and correction text. Clipboard denial opens the same text in a selectable dialog; copying never deletes comments.
- Mark reviewed to record the current content revision and automatically advance to the next unreviewed file. In compact landscape mode, Review only toggles the mark because Next is a separate adjacent control. Undo is offered. A later content change clears the review and marks existing comment anchors stale.
- Stage writes the whole file to the real Git index; once fully staged, the same control becomes Unstage and restores that path in the index from
HEADwithout changing its working copy. Review and stage are independent actions, and a stale operation is rejected instead of changing the index. - Commit is available from the changed-files drawer once at least one path is staged. It commits exactly the current Git index with the supplied message; unstaged working-tree edits remain local, and stale or conflicted states are rejected. Generate with Codex uses the signed-in local Codex CLI to propose an editable, single-line Conventional Commit from the staged patch; generation never stages or commits changes.
- When tracked or non-ignored
package.jsonfiles are present, the drawer adds a Commands view. Scripts are grouped by subproject, run with the package manager declared by the project or indicated by its nearest lockfile, and stream stdout and stderr into a reconnectable output sheet. Long-running scripts keep running when the sheet closes and can be stopped explicitly. - If Git fails or stops producing output, Couchview shows the operation-specific message instead of treating an empty response as a valid diff. Open Details to see a diagnostic ID, failure kind, exit code, and bounded Git output, or copy the complete diagnostic for reporting.
- Phone layouts share a centered floating action dock. Portrait keeps its roomier repository/file bars plus hunk and comment actions in the dock; compact landscape moves hunk/comments into its single top line and keeps only Previous, Review/Unreview, Stage/Unstage, and Next in the dock to protect vertical space.
- Use the minus and plus controls to adjust diff code from 9–24 px, or open Settings for all typography controls. The compact 11 px default and the selected preferences are stored in the browser.
Binary and metadata-only changes remain reviewable and stageable but do not accept line comments. Very large diffs show an explicit truncation warning.
On desktop Chrome or Edge over localhost, use the install icon in the address bar or the in-app install guidance. On iPhone or iPad, PWA installation requires Couchview to be served through HTTPS; a plain LAN-IP URL can open the review UI but is not a secure context. When HTTPS is available, open Couchview in Safari, tap Share, then Add to Home Screen. Launching the installed app uses the standalone, edge-to-edge interface.
Couchview always loads documents and repository data from the network. The service worker never caches index.html, handles document navigations, or caches /api responses, so an installed app cannot hide a Cloudflare Access sign-in behind a stale offline shell. It precaches only the versioned core JavaScript and CSS plus common JavaScript, TypeScript, JSX, TSX, JSON, CSS, HTML, and Markdown grammars. Other syntax assets load on demand and are warmed automatically when Couchview preloads adjacent diffs. The Ghostty terminal chunk, WASM runtime, and bundled Iosevka faces also stay out of the precache and load only when the tmux terminal is opened. When a new service worker is ready, Couchview asks before reloading the active review.
The server accepts only exact origins derived from the configured bind host and the machine's interfaces at startup, requires a per-launch CSRF header for writes and Codex generation, disables CORS, and serves a restrictive Content Security Policy. Git runs through simple-git with argument arrays, an inactivity timeout, bounded output, and validated repository-relative paths. Loopback binding is the default. Use --host 0.0.0.0 only to opt into LAN access on a trusted network; the tool can read selected repositories, stage files in their indexes, execute their declared package scripts, send staged change context to Codex, and—only with explicit non-loopback terminal opt-in—control tmux and its programs as the Couchview OS user.
Review flags, comments, and the saved-project catalog are stored in a user-only SQLite database using WAL mode:
${XDG_DATA_HOME:-$HOME/.local/share}/couchview/state.sqliteOnly an absolute XDG_DATA_HOME is honored; relative values fall back to $HOME/.local/share. Production and development servers share this database unless launched with different absolute data homes. Repository files are opened lazily, and concurrent local servers observe catalog and review changes through SQLite revisions. Package-run history and its bounded output are memory-only and disappear when the server exits.
COUCHVIEW_ROOT, COUCHVIEW_ALLOWED_ORIGINS, COUCHVIEW_TERMINAL, COUCHVIEW_TERMINAL_P2P, COUCHVIEW_TERMINAL_STUN, COUCHVIEW_REMOTE_BRIDGE, COUCHVIEW_REMOTE_BRIDGE_P2P, COUCHVIEW_REMOTE_BRIDGE_STUN, COUCHVIEW_REMOTE_BRIDGE_PORT, COUCHVIEW_REMOTE_BRIDGE_ORIGIN_ACCESS, PORT, and STATIC_DIR provide startup defaults when invoking the Bun server directly; command-line repository, port, terminal, and remote-bridge options take precedence. COUCHVIEW_ALLOWED_ORIGINS is a comma-separated list of exact trusted reverse-proxy origins and does not accept wildcards.
Package scripts execute on the host computer with the same operating-system permissions and environment as Couchview. The API accepts only exact scripts from detected manifests, takes no custom arguments or stdin, and protects Run and Stop with the same origin and CSRF checks as staging and committing. Those checks are not remote authentication: use package commands only with repositories and networks you trust.
Commit-message generation requires codex on the server PATH and an existing
codex login. Couchview sends Codex only a bounded staged patch, staged path metadata,
and up to ten recent commit subjects. The ephemeral Codex process runs from a temporary
non-repository directory in a read-only sandbox using gpt-5.6-luna; it cannot inspect
unstaged files through the supplied workspace.
The rebuild-and-restart action runs only Couchview's fixed bun run build command and
relaunches the same CLI path, repository, bind host, and port. It accepts no command or path
from the browser and uses the same origin and CSRF protections as other mutations.
Run the TypeScript checks, Bun unit/integration/UI tests, and production build:
bun run typecheck
bun test
bun run buildInstall the browser engines once, then run the mobile production suite:
bunx playwright install chromium webkit
bun run test:e2ePlaywright builds the PWA and starts its deterministic fixture on port 4174. It exercises 320 px, 375 px, and 430 px touch viewports plus compact landscape, multi-project history and tabs, horizontal containment, navigation, search, staging, comments, commits, and PWA behavior. A desktop Chromium project also runs the real Ghostty/WASM renderer against a deterministic terminal WebSocket and verifies lazy renderer and Iosevka loading, input, resize, Review handoff, and tmux session shutdown.
To point the browser suite at an already running instance instead:
PLAYWRIGHT_BASE_URL=http://127.0.0.1:4173 bun run test:e2esrc/client/contains the React review interface and PWA lifecycle UI.src/server/contains the Bun CLI/server, global SQLite catalog, Git boundary, validation, and event stream.src/shared/contracts.tsis the typed client/server API contract.scripts/dev.tssupervises the loopback-only Vite and Bun development processes.scripts/e2e-fixture.tsserves the deterministic production smoke fixture.
All shipped fonts, icons, scripts, and styles are local. Vite asset inlining is disabled so production can enforce its Content Security Policy without third-party origins or data: script/style assets.