| name | paper-review |
| description | Set up and run the botference document-review interface (Google-Docs-style comments on rendered LaTeX/Markdown, bots replying in threads, apply/commit of accepted changes) in any repo — and follow the round protocol when the user asks for a review round or tags you from the review page. |
paper-review — GDocs-style review of rendered sources, with bots
The engine is built and battle-tested. Canonical engine:
$BOTFERENCE_HOME/frontends/review/ (ships with botference). Architecture, decision history, and the unbuilt roadmap (CI
rounds, cloud version): design.md next to this file. This file tells
you how to USE the system.
Setting up a new project ("set up paper review in ")
Preferred: botference review [dir] — one command does all of the
below (engine copy, detection via scripts/review-detect.mjs with an
echoed summary, gitignore, build, serve; --share, --hosted,
--port N, --no-agents, --upgrade). Use the manual steps only when
the launcher isn't available:
- Copy the engine from the reference implementation into
<dir>/review/: build.mjs, server.mjs, chat.mjs, apply.mjs,
submit.mjs, init-config.mjs, bridge-system-prompt.md,
SCHEMA.md, assets/ (review.js, style.css). Never copy
review.config.json, state/, suggestions.json, or site/ —
those are per-project.
- Run
node review/init-config.mjs from <dir> (it inspects the
directory: master file via \documentclass, sections via
\input/\include order, bib, abbreviations, todo macros, figures
dir, a free port, and the bridge block). Show the user what was
detected and get their confirmation before proceeding — never
guess the master file silently.
- Add the gitignore block (see reference repo):
review/site/ and
review/state/* ignored EXCEPT state/users/ and
state/threads.json; suggestions.json tracked. Conversation must
travel through the repo; runtime files must not.
node review/build.mjs, then commit review/ ("Add review
interface"). Requires pandoc on PATH — check and tell the user if
missing.
Running it
Sync note: the workspace copies of this skill at
<vault>/botference/.claude/skills/paper-review/SKILL.md and
<vault>/botference/.agents/skills/paper-review/SKILL.md are plain
copies of this file — when you edit it (either copy in botference-main
or those), update all four.
botference review — build + serve; agents are on by default when
the machine can run them (python3 + a claude/codex CLI on
PATH — the launcher prints agents: on (…) or an agents: off
explanation). --no-agents forces off; --agents forces on (clear
error if impossible).
botference review --share — hosted mode behind a cloudflared quick
tunnel: respects REVIEW_PASSWORD or generates one, prints
share this: <url> password: <pw>; Ctrl-C stops server + tunnel
together. Missing cloudflared → install hint, keeps serving locally.
- Manual (no launcher):
node review/server.mjs — static + comment
mirror, no bots; --chat adds the bot bridge (needs
BOTFERENCE_HOME or the config's bridge.core_dir); --hosted adds
the REVIEW_PASSWORD gate, rate limits, and owner-gated summons. The
gate asks for a name and the password together (a name is not a
credential — it only picks which users/<handle>.json the guest
writes; asking on the gate is what makes hosted mode work on a phone,
where the sidebar picker is inside a drawer). The server serves ONLY
review/site/ + the figures dir (path-traversal guarded) — keep it
that way.
- Collaborator options: the owner's
--share URL (read + comment;
their agent summons queue for the owner to release), or clone the
repo and run the identical botference review (agents auto-detected;
without the CLIs they still read and comment), or plain
node review/server.mjs — then node review/submit.mjs [--push]
(commits only their own state/users/<handle>.json).
- The server is long-lived and owned by the human; don't leave your own
test servers running.
The interface contract (don't regress these)
- Comments are the only surface for discussing the document.
There is no chat-about-the-paper panel and there will not be one: it
was tried, it was confusing, it is rejected. Anything about the
text is an anchored margin thread. The one exception, added
deliberately, is the task console — a bottom-docked, collapsible,
owner-only, desktop-only bar for document-level instructions that
have no anchor text: "apply all", "commit", "restructure section 3",
"verify every citation resolves". Same strict routing (no
@claude/@codex/@all, no agent). The Changes widget lives inside
it, because committing is itself a document-level task. Interrupts
(permissions/choices) render on the card, in the console for
console-issued turns, or as sidebar toasts when off-page (120s
default-deny).
- Strict-but-social routing.
@claude routes to Claude alone; the
free-form footer handoff is disabled on review turns. To involve the
other agent, @-tag it in your thread reply — the server converts
that into a visible mention turn (depth cap 1 per thread per round).
@all engages both.
- Mentions fire only on explicit confirm (Done/Enter), never while
composing.
- Every author is visually distinct (stable accent color; Claude
coral, Codex blue, humans hashed hues); resolved comments live in a
reopenable resolved tab — and ANYONE may resolve/reopen ANY thread, attributed as "resolved by " (server-mediated; ownership boundary intact); humans can edit/delete their own entries
(
edited: true — treat an edited entry newer than your ack as new
input); author filter chips; light/dark/system theme toggle.
- Changes widget (inside the task console, owner-only) is the
commit workflow: accept → ⚡ Apply (gathers every participant's accepts; the owner's explicit decision wins conflicts) → read → ✓ Commit (or ↩ Revert).
Apply is deterministic AND author-agnostic (
apply.mjs: unique-span
replacement over bot cards and human suggestions, atomic bib
append, JSON-aware edits for config-key targets,
needs_manual_resolution on drift — never guessed). Apply and commit
are always separate actions. Out-of-band source edits surface in the
widget with a commit action.
- Everything is commentable. Paragraphs, figures, headings, list
items, block quotes, captions, table cells and the masthead title all
carry a
data-cid. blk-N is a POSITIONAL index over #paper p, #paper figure and every existing comment is anchored to one — so
that selector is frozen, and each newly anchorable type has its own
namespace (-hd-N, -misc-N). Never widen the blk selector.
- Humans suggest too. The composer has Comment and Suggest modes.
A human suggestion is an entry in that human's own
users/<handle>.json (suggestions.json stays bot-owned), renders
inline as del/ins in that human's colour through the same path as a
bot card, and flows through the same accept → Apply → Commit. Its
source span is resolved to something UNIQUE at compose time — widened
with context if ambiguous, anchored on the enclosing macro for
headings, aimed at the config title key when the masthead comes
from there — and the UI shows what it locked onto. See SCHEMA.md.
- Presence is coarse and private. Humans + agents in the top-right
cluster; state computed from real interaction, in server memory only,
never written to disk, symmetric for everyone, desktop-only.
- Three tiers, not two.
state/grants.json (owner-written) can let
a named handle summon agents within a visible daily cap. Apply,
Commit, Revert, model switching and permission answers stay
owner-only forever — a grant never confers them.
- Never invent a quota number. Neither provider exposes
subscription (Pro/Max, ChatGPT plan) quota to anything scriptable;
the Settings panel says so and points at
/usage. Do not
reverse-engineer internal endpoints to fake it.
- Calm UI: no
prompt()/alert()/confirm(), no scroll-jumps,
layout reflows (nothing overlays content), engine files stay 100%
document-agnostic (all specifics in review.config.json).
File ownership (never write another writer's file)
| File | Writer |
|---|
state/users/<handle>.json | that human's browser (via server) — comments, decisions, thread replies AND that human's suggestions |
suggestions.json | bots |
state/threads.json | bots |
state/ack.json | bots |
state/apply.json | apply.mjs (round ledger) |
state/grants.json, state/grant-usage.json | the owner, via the server |
site/, suggestions.js | build.mjs only |
| presence | nobody — server memory only, never a file |
Round protocol (when summoned — by an @tag or "read my comments")
- Read all
users/*.json, diff against your ack.json entry
(include entries with edited:true newer than your ack). Those
files now also carry humans' own user-suggestion entries — read
them as proposals, and do not duplicate one a human already made.
A DOCUMENT-LEVEL turn from the task console says so in its envelope:
answer in the turn text, do not write a thread entry for it.
- Reply to each new comment in
threads.json — length case-by-case,
never more than 6–8 sentences, no restating, no preamble. If a
text change is warranted, add a suggestion card
(current_text/proposed_text, reply_to) — see SCHEMA.md.
- Rejected cards get exactly one follow-up (concede or your single
best counter-argument).
- Never edit document sources during a round. Source changes go
through the user's Apply/Commit buttons only. Site styling/engine
fixes the user asks for directly may be made in
review/ files and
committed with a clear message.
- Update
ack.json last (crash mid-round → the round re-triggers).
Verification bar for any engine change
node --check all touched JS; rebuild; confirm served assets updated;
never kill the human's running server (verify on a throwaway port);
grep engine files clean of document-specific strings; if a change
touches document sources, compile the document (e.g. latexmk -pdf)
and inspect the output before claiming done.
Long-lived processes (servers, tunnels)
To serve or tunnel anything that must outlive your turn, use
botference service start <name> -- <command…> — or the one-shot
botference review --share --service / botference plan --share --service, which run the whole share (server + tunnel) as a managed
service, print the share this: <url> password: <pw> line, and
return control. Never bare background processes (&, nohup,
setsid by hand): they die with your turn's process-group teardown.
Manage what you started with botference service list|logs|stop.