--- 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: 1. Copy the engine from the reference implementation into `/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. 2. Run `node review/init-config.mjs` from `` (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. 3. 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. 4. `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 > `/botference/.claude/skills/paper-review/SKILL.md` and > `/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: password: `; 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/.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/.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/.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/.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") 1. 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. 2. 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. 3. Rejected cards get exactly one follow-up (concede or your single best counter-argument). 4. **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. 5. 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 -- ` — 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: password: ` 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`.