Rivets
Documentation

Previews

A task’s web server can appear in the task’s Preview tab on web, Mac, iPhone, and iPad. Everyone who can see the task can open it, live, while the agent works. Each task has its own ports, so two tasks can run the same dev server at once without colliding on 3000 or 5173.

Open the Preview tab

Choose + in the task’s tab strip and pick Preview. A task has one Preview tab. If several servers are running, choose which one to show from the tab’s menu.

When a run script that declares a preview is running and listening, Open preview appears beside the Run control and opens the Preview tab on that script’s server.

Until there is something to show, the tab says why and what to do. For example, it offers to start a script that declares a preview, or tells you how to declare one. It also tells you when the workspace is still being set up, the task is waiting for approval, or the worker is offline.

The tab has Reload and Open in browser. On Mac, iPhone, and iPad, the Preview tab keeps its page loaded when you switch tabs or tasks.

  • Pages on the dev server’s own address stay in the Preview tab.
  • On Mac, iPhone, and iPad, a link that leaves that address, including one that opens a new window, opens in a Browser tab on the same task and brings it to the front.
  • Pop-up windows opened by a script are blocked, and only http and https links are followed.

Give each task its own ports

Every task’s worktree gets a block of ten loopback ports, from RIVETS_PORT to RIVETS_PORT + 9. The block is between 20000 and 32759, skips ports something on the machine is already using, and stays with the task across worker restarts.

RIVETS_PORT is set for the task’s scripts, terminals, and agent. Start your dev server on it, bound to 127.0.0.1:

bun run dev -- --port $RIVETS_PORT

Agents are told about RIVETS_PORT and rivets preview, so an agent that starts a server for you uses its own port and registers it.

Show a server

A preview appears in the tab only while something accepts connections on its port, on 127.0.0.1 or ::1. Rivets never scans ports. A preview is always a port the task registered, in one of two ways.

From a run script

Add preview = true to a run script in the repository’s .rivets/settings.toml. The server is offered while the script runs:

[scripts.run.dev]
command = "bun run dev -- --port $RIVETS_PORT"
default = true
preview = true

To name it, open a path other than /, or show several servers from one script, use a table or a list of tables. See The .rivets directory.

From inside the task

For anything else, run rivets preview in the task’s terminal, or ask the agent to:

rivets preview                                     # the server on $RIVETS_PORT
rivets preview 6006 --name Storybook --path /docs/  # another port, with a label
rivets preview list                                # this task's registrations
rivets preview remove 6006                         # or --all
Option Default Meaning
[port] $RIVETS_PORT The port to show.
--name <name> Port <port> Label in the Preview tab.
--path </path> / Where the preview opens.

Registering a port again replaces its entry. rivets preview only works inside a task’s worktree on that worker. Registrations are kept in the task’s .context/previews.json, so they go away with the task.

The worker puts its own rivets command first on PATH in every terminal, script, and agent it starts, so the command works even with the Mac app’s bundled worker. If another rivets install comes first in an interactive shell, use "$RIVETS_CLI_DIR/rivets".

When a preview comes and goes

Rivets checks a registered port about every second until it answers, then every few seconds. A preview is withdrawn when its script stops, or when its port fails two checks in a row. A dev server that restarts after a file change does not flicker out of the list.

How a preview is served

Each preview is served from an address of its own, such as https://<label>-preview.rivets.dev, separate from the Rivets app. Its page never sees your Rivets session. Each viewer opens it with a one-use ticket that is checked against their access to the task’s repository. See Security.

  • Request paths are unchanged. The dev server is at the root of the preview’s address, and nothing in a response body is rewritten.
  • The dev server learns its public address. Every request carries X-Forwarded-Host, X-Forwarded-Proto, and X-Forwarded-Port for the preview’s address. A framework that trusts proxy headers, such as Rails, Laravel, Django, or Express with trust proxy, builds its links and redirects on the preview’s address.
  • Redirects stay inside the preview. A redirect to the preview’s own address, or to the server’s loopback port, is followed in the preview. A redirect anywhere else is blocked, and the Preview tab says the redirect left the preview.
  • Hot reload works. WebSocket connections, such as a dev server’s HMR, are forwarded too.

iOS Simulator on Mac

In the Mac app, the Preview tab can also show an iOS Simulator running on that Mac, so you can use a native app beside the task’s web previews.

  1. Open the Preview tab’s service menu and choose iOS Simulator.
  2. Choose a running device to connect to it, or Start to boot a stopped one.

The viewer takes touch and drag gestures, keyboard input, and hardware buttons. Choose Web previews to go back to the task’s servers. Disconnect simulator stops viewing, and leaves the simulator and its app running.

Requirements:

  • A Mac with Apple silicon.
  • Xcode with an iOS runtime.
  • Node.js 20 or later, with npx available in your login shell. The first use downloads the viewer through npm.

The simulator is on the Mac that shows Rivets, not on the task’s worker, and video and input never pass through the worker or Rivets’ servers. A simulator on a different Mac cannot be shown. On iPhone and iPad, the Preview tab shows web previews only and explains that simulator viewing needs a Mac.

Advanced: register a service by hand

A worker operator can register a service that no task starts, in ~/.rivets/preview-services.json, with mode 0600:

[
  {
    "id": "app",
    "worktreeId": "wt_example",
    "name": "Development app",
    "protocol": "http",
    "port": 3000,
    "basePath": "/"
  }
]

The worktree must belong to that worker. These entries are offered whether or not anything is listening, and the file is re-read when it changes. For managed environments, the RIVETS_PREVIEW_SERVICES environment variable takes the same JSON and takes precedence over the file.

Type to search the docs.