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 an issue to a review slot is ordinary
tag routing: a status
requires tags, a workspace carries tags, and the issue 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 issues 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 an issue 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 an issue 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 issue'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 } # dev slot
- { name: bravo, machine: hub } # dev slot
- { name: review-0, machine: hub, 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 an issue enters the status: it loads the issue 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 issues surface in the sidebar under two dedicated sections, split by whether an issue 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 issue'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 issue 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 issue (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.
The daemon auto-loads queued review-column issues onto idle review-tagged
slots each poller tick (autoload_review_queue), so a queued branch normally
lands on a free slot without any human action. The confirm popup is the
manual path: selecting a Queued row raises a "Load onto a review
workspace?" prompt and, on confirm, loads the branch onto a free
review-tagged slot (never the dev pane that built it) — useful for loading
a queued issue while the slots are otherwise busy. 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 issue 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. By default it launches git's configured diff tool (diff.tool, ordiff.guitoolfor a GUI tool) in directory-diff mode; with none configured the panel reports a short error rather than launching. A tool that reviews a revision range instead of two directory trees (such asskim) needs areview.diff_commandoverride inproject.yaml— see Project config. A diff tool that exits without rendering leaves a short message in the pane rather than only git warnings. - ✍️ 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 issue 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 issue body and the issue is bounced back to the workflow's ready status for another pass. An empty reason can't submit.
- ✅ Approve — move the issue 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 issue 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 issue 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 issue loads onto a freereview-tagged workspace and the Review agent boots the branch from the resolvedreview:recipe. Queued review issues are paired with idle review slots in declaration order — the dev workspace that built the branch is not preferred (it is typically not review-tagged); the only reuse is resuming a slot the issue is already serving on. If every matching slot is busy, the issue sits in Queued for Review until one frees — nothing is preempted. - A human inspects. You open the issue'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 issue sits in the
handoff-category review status throughout.
Review and Zen Mode
Routing an issue to a review workspace is the human path — the whole point is
human eyes on the running app. Whether such an issue 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 issue 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.