# Review workspaces

Review routing is ordinary tag primitives: a status requires a tag, a workspace carries it, and the task loads onto a matching slot. On top of that, Shelbi ships a dedicated `review:` serve recipe that boots the branch, a review sidebar that lists tasks Ready and Queued for review, and a review interface for clicking through the running app and accepting or rejecting it.

Most real review of an app or a site means **running it**: booting the dev
server, hitting a URL, clicking through the change, not just reading a diff.
The *routing* that gets a task to a review slot is ordinary
[tag routing](/docs/concepts/workspaces#tags-capability-labels): a **status
requires tags**, a **workspace carries tags**, and the task loads onto a free
slot whose effective tags match. On top of that generic routing, Shelbi ships
three pieces of review-specific machinery: a workflow `review:` **serve recipe**
that boots the branch, a **review sidebar** listing tasks Ready and Queued for
review, and a **review interface** for running the change and accepting or
rejecting it.

This page covers the review tag, the serve recipe, and the review surfaces in
the TUI. The generic tag and slot mechanics they build on — effective tags,
superset routing, and `$SLOT` — live in
[Workspaces](/docs/concepts/workspaces#tags-capability-labels).

## The review tag

Review routing uses one tag by convention: `review`. A workflow's review
[status](/docs/configuration/workflow#statuses) requires it, and every slot
meant to run review work carries it:

```yaml
# workflows/default.yaml — the review status requires the tag
statuses:
  - { id: review, owner: user, agent: review, tags: [review] }
```

```yaml
# project.yaml — tag the machine (or individual slots) so its slots carry it
machines:
  - name: hub
    kind: local
    work_dir: ~/Workspaces/myapp
    tags: [review]              # every slot on hub inherits `review`
```

When a task enters the review status, the orchestrator routes it to a free
workspace whose effective tags are a superset of `{review}` — the same
[superset routing](/docs/concepts/workspaces#status-tag-routing) every tag uses.
Nothing branches on the literal word "review"; it is just the tag this workflow
happens to require. If no declared workspace carries it, the load fails loudly
rather than running review work on a general slot.

## Serving the branch: the `review:` block

Routing gets a task onto a review slot. **Serving** the branch — booting the
dev server so a human can click through it — is best expressed with the
workflow's dedicated [`review:`](/docs/configuration/workflow#review) block.
This is the recommended way to stand up a review server. Shelbi resolves the
recipe's `$SLOT` / `$PORT` placeholders against the review slot's port and
injects the resolved recipe into the [Review agent](/docs/concepts/agents)'s
dispatch prompt; the agent runs it verbatim, health-checks it, and hands back
a URL.

```yaml
# workflows/default.yaml
review:
  workdir: site                                # relative to the worktree root
  setup: npm install --no-audit --no-fund      # one-shot; must exit 0 before serving
  serve: npm run dev -- -p $SLOT               # binds the dev server to the slot's port
  ready: curl -sf http://localhost:$SLOT       # readiness probe, polled until it exits 0
  url: http://localhost:$SLOT                  # reviewable URL; gates the "Open Browser" action
```

The `review:` block carries exactly these fields — matching the
[`ReviewServe`](/docs/configuration/workflow#review) struct in source:

- **`workdir`** — subdirectory to run the recipe in, relative to the worktree
  root. Omitted, it runs at the root.
- **`setup`** — a one-shot install/build command that must exit 0 before
  serving. Omitted, setup is skipped.
- **`serve`** (required) — the command that starts the dev server, bound to
  the slot's port.
- **`ready`** — a readiness probe polled until it exits 0. Omitted, there's
  no HTTP probe.
- **`url`** — the reviewable URL handed to the human once the server is up. It
  also gates the review interface's **Open Browser** action.

Because the recipe lives on the workflow, a monorepo's `app` / `site` / `docs`
workflows each serve their own subdirectory on the review slot's port without
colliding. When a workflow declares **no** `review:` block, the Review agent
does a **diff-only** review: it does not auto-detect a framework or boot a
default-port server.

### `$SLOT` (transition env) vs `$PORT` (review template)

Both spellings exist, and they are not the same mechanism:

- In a transition's [`run` / `ready`](#transition-commands-run-ready-teardown)
  commands, **`$SLOT`** is a shell environment variable holding the
  workspace's numeric slot. You derive a port from it yourself with shell
  arithmetic (`$((3000 + $SLOT))`).
- In the `review:` block, **`$SLOT` and `$PORT` are interchangeable template
  placeholders**, both resolved by Shelbi to the review slot's port *before*
  the recipe reaches the agent. Both the `$X` and `${X}` spellings are
  substituted (see `substitute_review_url` in source); a fixed URL with no
  placeholder round-trips unchanged. There is no shell arithmetic here — the
  agent receives the already-substituted string.

## Transition commands: run, ready, teardown

The `review:` block is the preferred way to serve a review branch, but a
status's **transitions** can also run arbitrary commands, and that's the right
tool for hub-side side-effects that aren't the review server itself. A
[transition](/docs/configuration/workflow#run-ready-and-teardown) can carry:

- **`run`** — shell commands executed in order, in the task's worktree, on the
  assigned workspace's machine. They run after the edge's git `actions` and
  share the same short-circuit contract.
- **`ready`** — a command polled until it exits 0 (bounded by `ready_timeout`,
  default 90s), so a launched server can be confirmed up before the human is
  handed the URL.
- **teardown** — expressed as the `run` of the *exit* transition; there is no
  separate teardown hook.

Each command runs synchronously, so a long-lived server must background itself
(`… &`, `nohup`, a detached pane). The edge is entered the moment the launcher
returns; `ready` is what confirms the server actually answers.

Unlike the `review:` block, a transition `run:` server runs **hub-side and
declaratively** — Shelbi executes it, not the Review agent — so it can't
health-check, summarize a failure, or apply a human's tweak. Prefer `review:`
for the serve recipe; keep transition `run:` for the side-effects around it.

## A review column, from these pieces

Here is a complete review column: a tagged machine, a tagged status, a
`review:` serve recipe, and an accept edge that merges. Only the routing is
generic workflow config; the serve recipe and the review interface are the
review-specific machinery layered on top.

```yaml
# project.yaml — tag the machine (or individual slots)
machines:
  - name: hub
    kind: local
    work_dir: ~/Workspaces/myapp
    tags: [review]

workspaces:
  - { name: alpha,    machine: hub, runner: claude }                 # dev slot
  - { name: bravo,    machine: hub, runner: claude }                 # dev slot
  - { name: review-0, machine: hub, runner: claude, tags: [review], slot: 3000 }
```

```yaml
# workflows/default.yaml — the review status requires the tag, and the
# review: block tells the Review agent how to boot the branch.
statuses:
  - { id: in-progress, owner: agent, agent: developer }
  - { id: review,      owner: user,  agent: review, tags: [review] }
  - { id: done,        owner: user }

transitions:
  - from: review, to: done, actions: [merge, delete_branch]

review:
  setup: npm install --no-audit --no-fund
  serve: npm run dev -- -p $PORT
  ready: curl -sf http://localhost:$PORT
  url: http://localhost:$PORT
```

With `review-0` on slot `3000`, `$PORT` resolves to `3000` and the agent
serves the branch on `:3000`; a second review slot set to `3010` would serve
there. The port is deterministic and collision-free because it's derived from
the slot.

<Callout type="note" title="The status's agent runs the recipe">

Routing does two things when a task enters the status: it loads the task onto
the matching workspace running the status's [`agent`](/docs/configuration/workflow#statuses),
**and** it hands that agent the resolved `review:` recipe. Shelbi ships a
built-in `review` agent whose charter is "make the change runnable for a human,
don't code" — name it here, or point `agent:` at any agent you've authored.
See [agents](/docs/concepts/agents).

</Callout>

## The review sidebar

Review-status tasks surface in the sidebar under two dedicated sections, split
by whether a task is loaded on a review slot yet:

```text
 — Ready for Review —
 ✓ Polish dark-mode toggle              hub:review-0
   jlong/dark-mode-toggle-polish

 — Queued for Review —
 · Fix cookie domain bug
   jlong/fix-cookie-domain-bug
```

- **Ready for Review** (`✓`, cyan) — the task's `assigned_to` names a
  `review`-tagged workspace, so the branch is loaded on a review slot and
  serving. Each entry is a two-line row: line 1 is the task title with a
  right-aligned **location badge** (the `machine:workspace` it's loaded on,
  e.g. `hub:review-0`); line 2 is the branch, dim.
- **Queued for Review** (`·`, dim) — every other Review-status task
  (unassigned, or still pinned to the dev workspace that produced it), waiting
  for a free review slot. No location badge, because nothing is serving yet.

A `review`-tagged slot **never appears under the `— Workspaces —` section**;
its capacity surfaces exclusively through these two review sections. Dev
workspaces list under `— Workspaces —` as usual.

Selecting a **Queued** row raises a "Load onto a review workspace?" confirm
popup and, on confirm, loads the branch onto a free `review`-tagged slot (never
the dev pane that built it). Selecting a **Ready** row opens its review
interface directly.

<Callout type="note" title="Sidebar collapse persists">

Machine groups under `— Workspaces —` can be collapsed (Space / Enter on a
machine header). The set of collapsed machine names is persisted to
`~/.shelbi/state.json` under `sidebar.collapsed_machines` (the `SidebarPrefs`
struct), so the choice survives a sidebar respawn and follows you across
projects that share a machine name. Machine names not present in the current
project are ignored, not dropped, so re-adding the machine restores its prior
state.

</Callout>

## The review interface

Opening a Ready task launches the **review interface** — a two-column tmux
layout: the **review panel** on the left and the swappable review content on
the right. The panel is the review window's own navigation, so the global nav
sidebar stays docked in the dashboard. The panel is where you drive the review:

- A square **back button** at the top (a back-arrow glyph) that switches focus
  back to the dashboard window. It leaves the review interface loaded, so you
  can return to it from the sidebar.
- A **header** with the review status (`Ready for review`) and the review
  worktree's folder (`📂`) — click it to reveal the worktree in your OS file
  manager.
- A **view switcher** that swaps the right-hand content pane:
  - **🤓 Chat with Reviewer** (default) — talk to the Review agent.
  - **🔀 View Diff** — open your system diff tool over the review branch's
    changes (the same `merge-base(base, HEAD)..HEAD` range `shelbi diff`
    shows) in the main pane. It launches git's configured diff tool
    (`diff.tool`, or `diff.guitool` for a GUI tool); with none configured the
    panel reports a short error rather than launching.
  - **✍️ Edit in `<editor>`** — open the worktree in your configured editor
    (see below).
  - **🌐 Open Browser** — open the resolved review `url` in your system
    browser. This entry renders **only** when the workflow declares a review
    URL.
- An **Actions** group:
  - **✅ Approve** — move the task one column forward along the normal accept
    edge (`review → done` in the default workflow), firing that edge's
    `actions` (e.g. `merge`, `delete_branch`), then tear the interface down.
  - **❌ Reject** — open a type-the-reason dialog; on submit, the reason is
    appended to the task body and the task is bounced back to the workflow's
    ready status for another pass. An empty reason can't submit.

### The review editor

The **Edit in `<editor>`** view launches a **hub-wide** editor, configured once
in [`~/.shelbi/config.yaml`](/docs/configuration/global#configyaml) under the
`editor` key, so a reviewer's editor choice follows them across every project.
The value may be a bare command (`hx`) or a command with flags (`code --wait`).
Resolution order is:

1. `~/.shelbi/config.yaml`'s `editor`,
2. the `$EDITOR` environment variable,
3. `vim`.

The switch label is derived from the program's basename, first letter
upper-cased — `hx` shows as **Edit in Hx**, `code --wait` as **Edit in Code**.

## The load → serve → inspect flow

End to end, a finished task reaches a human like this:

1. **A dev workspace finishes.** It writes its review-ready marker; the hub
   poller rebases the branch onto the base branch and moves the task into the
   review status. The finishing workspace closes its own session and returns
   to idle (see [Workspaces](/docs/concepts/workspaces#how-a-task-completes)).
2. **The orchestrator routes it.** The review status requires `tags: [review]`,
   so the task loads onto a free `review`-tagged workspace (preferring the one
   it ran on) and the Review agent boots the branch from the resolved `review:`
   recipe. If every matching slot is busy, the task sits in **Queued for
   Review** until one frees — nothing is preempted.
3. **A human inspects.** You open the task's review interface from the sidebar,
   click through the running app at the served URL, and decide: **Approve**
   (move to Done) or **Reject** (bounce back with a reason). Each move fires
   that edge's `actions` and `run`.

The board's category vocabulary is unchanged: the task sits in the
`handoff`-category review status throughout.

## Review and Zen Mode

Routing a task to a review workspace is the *human* path — the whole point is
human eyes on the running app. Whether such a task is eligible for
[Zen Mode](/docs/concepts/zen-mode)'s auto-merge is governed by the ordinary
action-based rule: Zen's high-confidence bar fires on any transition whose
`actions` include `merge`. Keep `merge` off the *enter*-review edge and on the
*accept* edge (as above) so the orchestrator holds the task for you rather than
crossing the accept boundary on its own.

## See also

- [Workflow config](/docs/configuration/workflow) — the status `tags`, the
  `review:` serve block, and transition `run` / `ready` / `ready_timeout`.
- [Project config](/docs/configuration/project) — machine and workspace
  `tags`, and workspace `slot`.
- [Global config](/docs/configuration/global) — the hub-wide `config.yaml`
  `editor` the review interface's "Edit in `<editor>`" view uses.
- [Workspaces](/docs/concepts/workspaces) — the pool model these slots extend.
- [Agents](/docs/concepts/agents) — the role that runs inside a workspace,
  including the built-in `review` agent.
- [Set up review workspaces](/docs/guides/getting-started/review-workspaces) —
  the step-by-step walkthrough.
