Review workspaces

View as markdown

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

The review tag

Review routing uses one tag by convention: review. A workflow's review status requires it, and every slot meant to run review work carries it:

# workflows/default.yaml — the review status requires the tag
statuses:
  - { id: review, owner: user, agent: review, tags: [review] }
# 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 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: 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's dispatch prompt; the agent runs it verbatim, health-checks it, and hands back a URL.

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

# 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 }
# 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.

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

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:

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

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.

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 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).
  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'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 — the status tags, the review: serve block, and transition run / ready / ready_timeout.
  • Project config — machine and workspace tags, and workspace slot.
  • Global config — the hub-wide config.yaml editor the review interface's "Edit in <editor>" view uses.
  • Workspaces — the pool model these slots extend.
  • Agents — the role that runs inside a workspace, including the built-in review agent.
  • Set up review workspaces — the step-by-step walkthrough.