Review workspaces
View as markdownMost 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" actionThe 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/readycommands,$SLOTis 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,$SLOTand$PORTare interchangeable template placeholders, both resolved by Shelbi to the review slot's port before the recipe reaches the agent. Both the$Xand${X}spellings are substituted (seesubstitute_review_urlin 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 gitactionsand share the same short-circuit contract.ready— a command polled until it exits 0 (bounded byready_timeout, default 90s), so a launched server can be confirmed up before the human is handed the URL.- teardown — expressed as the
runof 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:$PORTWith 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'sassigned_tonames areview-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 (themachine:workspaceit'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)..HEADrangeshelbi diffshows) in the main pane. It launches git's configured diff tool (diff.tool, ordiff.guitoolfor 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
urlin 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 → donein the default workflow), firing that edge'sactions(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.
- ✅ Approve — move the task one column forward along the normal accept
edge (
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:
~/.shelbi/config.yaml'seditor,- the
$EDITORenvironment variable, 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:
- 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).
- The orchestrator routes it. The review status requires
tags: [review], so the task loads onto a freereview-tagged workspace (preferring the one it ran on) and the Review agent boots the branch from the resolvedreview:recipe. If every matching slot is busy, the task sits in Queued for Review until one frees — nothing is preempted. - 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
actionsandrun.
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, thereview:serve block, and transitionrun/ready/ready_timeout. - Project config — machine and workspace
tags, and workspaceslot. - Global config — the hub-wide
config.yamleditorthe 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
reviewagent. - Set up review workspaces — the step-by-step walkthrough.