roughbook
v0.3.16
Published
Roughbook studio: design iterations inside your own repo, with the coding agent and editor you already use
Readme
| roughbook doctor [--json] [--report] [--skip-build] [--yes] | Checks this repo's preview setup against the contract (below) and says what to fix; --json for a team's CI, --report names the redacted bundle it writes for a support ticket, --skip-build leaves the production build out and records the repo as unverified, --yes approves the environment's commands on this machine |
| roughbook setup [--fix \| --apply \| --upgrade] | Has your agent build this repo's preview setup in an interactive session (any stack); --fix sends doctor's failures back to it, --apply writes the templates in rough/setup/ into place without an agent, --upgrade rewrites a recipe's setup |
| roughbook uninstall | Removes the preview setup from the app, exactly the files rough/environment.md lists and a recipe's own, and nothing else; rough/ stays |
Roughbook studio
Design iterations inside your own repo, with the coding agent and editor you already use. The
studio links a repo to a Roughbook project, writes each iteration's prompt into rough/, hands it
to your agent, checks what came back, and lets you review, select and promote from a local UI.
Your code stays on your machine: the server only ever sees ids, review decisions and run metadata.
Runs on Windows, macOS and Linux: from npm with Node 22 or later, or as a standalone binary that needs no Node.js.
Install
npm install -g roughbook
roughbook login
roughbook init # in your app's repo
roughbook startInstalled once, roughbook runs as it is, with no version and no npx. npm install -g
roughbook@latest updates it. npx roughbook <command> works without installing, but npx keeps
its own copy and asks before fetching each new one.
Without Node.js
PHP, Python, Ruby, Go, Flutter, iOS and Android teams often have no Node.js, so the studio also ships as one executable that carries its own runtime (decision 35 of the frameworks plan):
# macOS and Linux
curl -fsSL https://github.com/bharatnow/roughbook-studio/releases/latest/download/install.sh | sh
# Windows, in PowerShell
irm https://github.com/bharatnow/roughbook-studio/releases/latest/download/install.ps1 | iex
# or a package manager
brew install bharatnow/roughbook/roughbook
winget install NuveraSoft.Roughbook
scoop bucket add roughbook https://github.com/bharatnow/scoop-roughbook
scoop install roughbookThe binaries are for Windows x64, macOS (Apple silicon and Intel) and Linux x64 and arm64 with
glibc (not Alpine). The first run unpacks the same files npm install -g roughbook would put down
into a cache folder (~/.cache/roughbook, ~/Library/Caches/roughbook or
%LOCALAPPDATA%\roughbook\cache; ROUGHBOOK_CACHE_DIR names another), once per version. The
binary runs everything the npm package does; npm, npx and an agent's npm-installed script, which
are not inside it, run on the Node.js on the PATH when a stack needs them. packaging/README.md
says how the binaries are built and released.
Commands
| Command | Does |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| roughbook login [--server <url>] [--no-browser] | Signs this machine in: shows a code to enter at the web's /link page and opens it |
| roughbook new [<recipe>] [<folder>] | Starts an app of a recipe's stack in an empty folder with the stack's own scaffold (create-vite, composer create-project, …), starts git, then runs init in it; takes init's --project, --new, --org and --yes |
| roughbook init | In an app's repo: links it to a project and prepares it, showing every change first |
| roughbook start [--port <port>] [--dev-url <url>] | Opens the local UI in your browser: the project's components, their iterations live, compare, review, runs with the built-in terminal, Done and promote; starts the app with the environment's approved start command unless something answers at its address (--no-app leaves it to you) |
| roughbook run <page>/<component> … | Starts iterations: --direction <idea> (up to eight), --fork <iteration>, or --refine <iteration> --intent <text>; opens your agent in a terminal (--agent claude-code, codex, antigravity-cli or cursor-cli), --headless runs it unattended, and --agent cursor (or vscode, antigravity, windsurf, other) opens the prompt in that editor |
| roughbook done [<run or iteration>] | Finishes a run: checks the iteration, lists files changed outside it and asks whether they were yours (--mine <path> names them), takes screenshots, reports it |
| roughbook screenshot [<page>/<component>/<iteration>] | Takes an iteration's screenshots again (the last finished run's when none is named), as the iteration screen's Take screenshots does |
| roughbook review <page>/<component>/<iteration> … | --select, --reject <reason> [--why <text>], or --note <observation>, and --rating <1-5\|none> with either or alone; saved on the server, so the next prompt remembers |
| roughbook revert <page>/<component>/<iteration> <file> | Puts back a file the run changed outside its iteration |
| roughbook pull <page>/<component>[/<sketch>] | Lists the component's web sketches, or brings one into rough/ as the next local iteration: copied where it renders as it is, else ported into your stack by your agent (--port, --add) |
| roughbook promote <page>/<component> | Hands the selected iteration to your agent to write the real component (--to <file>, --iteration <id>) |
| roughbook agents [--default <agent>] | Lists the coding agents and editors found here, their sign-in and whether they can run unattended; sets the default |
| roughbook mcp install [<editor>…] | Lets your editors' and agents' own agents find, check and finish runs themselves: adds roughbook mcp, the MCP server, to each one found here, or to those named |
| roughbook status | The sign-in, the analytics answer, the linked project, and what is waiting to be sent |
| roughbook telemetry [on\|off] | Shares usage analytics from this studio, or stops; with no argument, says which |
| roughbook logout | Signs this machine out, here and on the server |
init works in any repo with git, package.json or not. It first reads the repo's marker files and
says what it found: the stack as short tokens (laravel-13, blade, php-8; next-16,
react-19), the family of checks that goes with it, the package manager, a container setup and
the repo's own agent rule files. Those tokens and the family go to the server with the link, and
nothing else about the repo. It asks which project the repo works on, by its number, name or slug,
or n for a new one, whose name it shows back before creating it (or --project <slug>,
--new <name>, with --org <id> when two organizations share a slug) and whether to commit
rough/ (--commit, the default, or --no-commit); --yes applies without asking; --recipe
<id> names the recipe over what detection guessed. --scratch <dir> first makes a minimal Next.js
16 app with Tailwind v4 and a git repository there, without the network (roughbook new next runs
create-next-app instead). For a Next.js app it then:
- writes
rough/withREADME.md,AGENTS.md(the iteration rules every agent reads),CLAUDE.md(pointing at them) androughbook.json(the organization and project, no paths); - adds the development-only preview route under the App Router,
rough/preview.tsxfor the app's providers, and the generatedrough/preview-registry.ts; - excludes
rough/and the route from TypeScript and ESLint, and adds an@sourceline so Tailwind v4 sees the classes inrough/; - writes
rough/tsconfig.json, which extends the app's so editors check iterations with its paths and types while the build still skips them; - tells git what to keep: run state, screenshots, the registry and the route are never committed,
and
rough/itself only when you choose not to commit it.
A file the studio did not write is never replaced; it is listed for you to change by hand. Running
init again changes nothing, and it adopts a repo prepared by Roughbook v2.
The preview route above is the Next.js recipe's; every other recipe writes its own (below). When no
recipe fits the stack, init writes the
workspace alone and offers the setup run (roughbook setup): an interactive session of your
agent that reads the repo, writes rough/environment.md and the templates in rough/setup/, builds
the preview setup in the repo's own stack and a sample iteration for doctor, and may run the
app's commands; it never runs unattended. When it says the setup is ready, start the app and run
roughbook doctor; roughbook setup --fix takes its findings back to the agent. On a teammate's
clone, init finds rough/environment.md, shows the templates it is about to apply and the
commands it is about to approve, applies and approves them, and runs doctor when the app answers.
The environment
rough/environment.md is the repo's environment: a header between --- lines the studio reads,
then prose for agents, written by the setup run (or implied by a recipe), reviewed by you and
committed. The header is key: value lines and key: with - item lines. Its fields: contract
(1), recipe (one of the recipes below, or none when your agent built the setup), stack and family, mode
(live, snapshot or standalone), entry (the file every iteration has), sources and
code (extensions the checks and the import check read), placement, wrapper, runtime
(local, or container @ <url>), shell, start (<command> @ <url>, or the address alone),
preview (/path/{key}), snapshot (the command that renders keys to images, below) and
device (the one that opens a key on a simulator or device, {key} in it), viewports
(name WxH [mobile]), variants (light, dark, a
language tag such as en or ar, large-text, or a word of the setup's own, which reach the
preview as roughbook-scheme, roughbook-dir, roughbook-lang, roughbook-text and
roughbook-variant query parameters), reload (app when the stack reloads a changed iteration
itself, studio when the frame reloads it on a change), stylesheets and public (standalone
mode, below), ignore, overlays,
fixture, build and buildOutput, gate (build, runtime or both), production
(<command> @ <url>, or the address alone for an app that already runs in production there, such
as a store's live theme), checks, timeoutStart, timeoutPreview and timeoutBuild, promote and
promoteExtensions, assets and assetsCopy, copy (inline, or localized <file>), terms
(page: view, component: partial), workshop, setupFiles and devDependencies. A command
differs on Windows through its …Windows twin (shellWindows, startWindows, snapshotWindows,
deviceWindows, buildWindows, productionWindows, checksWindows). With recipe: none,
entry, sources and gate are required, with start and preview for a live preview and
snapshot in snapshot mode. A header the studio cannot read is
refused with the field named, and nothing in the prose is ever interpreted.
Every command the environment names runs in the shell it declares (none starts the program by
name, as before; bash, sh, zsh, powershell, pwsh or cmd are started by name with the
line as their one command), never the machine's default shell, one at a time per repo, and only
after you have approved it on this machine: init, setup --apply and doctor show each command
with its shell and ask, and keep the approval by its hash in rough/.roughbook/approvals.json, so
a command changed by a commit is asked again and a committed file never runs code on a teammate's
machine unseen. With no terminal to ask in (an agent's shell, CI, an IDE task), nothing is
approved: init, setup --apply and doctor approve only with --yes, start runs no command
until one is approved, and new makes nothing without --yes.
setupFiles lists every file the setup adds or changes outside rough/, each with its template in
rough/setup/: a whole file at rough/setup/<path>, or lines added to a file the app already has
as <path> (snippet) with the lines at rough/setup/<path>.snippet. setup --apply and init on
a clone write them into place (a snippet is appended once), and uninstall takes exactly them out.
rough/setup/sample/good/ and rough/setup/sample/broken/ hold the iterations doctor renders
for a repo with no recipe, with {{roughbook:marker}} and {{roughbook:fixture}} where their text
shows.
Local UI
roughbook start serves the local UI on 127.0.0.1 (port 4848, or --port, which is kept
for next time) and opens it. It opens on the project's canvas, as the web does: pages as sections,
components as frames, and each iteration in rough/ as a card with its last screenshot, beside
the web sketches not pulled yet (each with Pull) and the runs an agent is making now. A prompt at
the foot starts new directions, forks and refinements with the agent of your choice; the
selection's toolbar has each run's Done, Stop and Copy prompt, Compare (or Shift-click), the
component's brief (read-only here, with a link to edit it on the web) and Promote; and ⌘K or
Ctrl K finds every command. An iteration opens on its stage, in the preview frame
(@roughbook/ui's PreviewFrame, the same for every stack and mode): the page from the app's
own dev server (--dev-url, ROUGHBOOK_DEV_URL, or the environment's start address) at each
of the environment's viewports and in each of its variants, its state (starting, making, ready,
failed, missing) with its message and the app's last line while it starts, Reload, Open, and the
last screenshot when the app is not answering; beside it the review drawer, notes, select, reject
and refine, and its run's report with Put it back for files changed outside it. The frame and the
stage say the stack's own words for page and component beside Roughbook's (terms). Compare
shows each column in the same frame, loaded only while on screen and four at most; the rest show
their stills. One status dot in the bar holds the app's state (with Start the app when it is not
answering), the outbox, the agents and the version. While it runs, it keeps
rough/preview-registry.ts in step with the iteration folders, sends what waits in the outbox,
reads rough/environment.md again whenever it changes, watches the iteration folders (the frame
reloads on a change where the environment says reload: studio, and the changed iteration's
screenshots are taken again once its files have been still for a few seconds, unless an agent is
still writing it), and asks the app every few seconds whether it answers.
Runs open in the built-in terminal, a dock at the foot of the canvas with one tab per session
(xterm.js on node-pty), or in your own terminal if you choose. A session runs the agent itself, never a shell, and belongs to its run:
closing or reloading the page leaves it running, and joining again replays what it printed. It is
used on Windows, macOS and glibc Linux, where node-pty installs and starts; elsewhere, and with
ROUGHBOOK_TERMINAL=external, runs open in your own terminal. It comes as @lydell/node-pty,
node-pty's own files split into one package per platform with no install script, so npm has
none to run or warn about.
The server answers only 127.0.0.1 and localhost as its Host, and its API and terminal only
the page it opened: a token made at each start reaches the page in the link's fragment, and every
call carries it. A card's <img>, which cannot send it, reads its screenshot with a key derived
from the token that reads screenshots and nothing else. Iterations load the project's and library's asset files from
http://127.0.0.1:<port>/api/files/…, which this server fetches from the backend with your
sign-in; prompts name that address, so keep the port.
Development
npm install
npm run dev -- --help| Script | Does |
| -------------------- | -------------------------------------------------------- |
| npm run dev -- <…> | Runs the CLI from source through tsx |
| npm test | node --test over src/**/*.test.ts, never the network |
| npm run type-check | tsc --noEmit for the CLI and the local UI |
| npm run lint | ESLint, including the studio's own rules (AGENTS.md) |
| npm run build | Compiles to dist/ and the UI to dist/ui/ |
| npm run build:ui | The local UI alone, with Vite |
The backend is roughbook-backend. The studio talks to https://rough-api.nuverasoft.com unless
ROUGHBOOK_SERVER or roughbook login --server says otherwise, such as http://localhost:4000
for a backend on this machine; anything but this machine must be https.
Where things are kept
| What | Where |
| ------------------------------ | ---------------------------------------------------- |
| Your sign-in (install token) | The user config folder (below), readable only by you |
| The repo's link to its project | rough/roughbook.json, committed with rough/ |
| The default agent, local port | settings.json in the user config folder |
| Your analytics answer | settings.json too; the backend keeps it as well |
| Analytics events not yet sent | events.json in the user config folder |
| Runs, snapshots, the outbox | rough/.roughbook/, never committed |
| Approved commands, by hash | rough/.roughbook/approvals.json, never committed |
| Setup runs and doctor's report | rough/.roughbook/setup/, doctor-report.json |
| The repo's environment | rough/environment.md and rough/setup/, committed |
| Screenshots | rough/.screenshots/, never committed |
The user config folder is %APPDATA%\Roughbook on Windows, ~/Library/Application Support/Roughbook
on macOS, and $XDG_CONFIG_HOME/roughbook (or ~/.config/roughbook) on Linux.
ROUGHBOOK_CONFIG_DIR overrides it.
Usage analytics are opt-in: roughbook login asks, and roughbook telemetry on or off changes
the answer. With a yes, the backend records what this studio asks it to do (runs, ratings,
selections, pulls, promotions) as ids, counts and codes, and the studio sends the two events only it
sees: that the local UI started, with the agents it found, and a failure's error code. Never code,
prompts, names, paths or anything you type; the web's /analytics page lists every event.
ROUGHBOOK_TELEMETRY=off turns it all off on this machine, whatever the answer.
Runs
A run starts with the backend: it allocates the iteration and compiles the prompt. The studio then
records every file in the repo by hash (copying aside any with uncommitted changes), writes the
iteration's folder (a placeholder, or a copy of the parent for a fork or refinement) and the prompt
in rough/<page>/<component>/prompts/<run>.md, and hands the agent one line:
Follow the instructions in <prompt file>. At Done it applies the backend's checks (the entry
file exists and is not the placeholder, a fork changed from its parent, the approved headline
appears) and its own (nothing outside rough/ imports from it), lists every file changed outside the run's folder, and sends the outcome, warning codes and counts. What you changed yourself while the run was open is not the agent's: done shows those files and asks, --mine <path> names them (a file, a folder, or . for all), the local UI's Done lists them to tick, and finish_run takes mine; they are kept on the run and left out of the count. The run records who finished it: the agent through MCP, done, the local UI, or the agent exiting. Any such file can be put back:
a new one is removed, an uncommitted one comes back from its copy, a committed one from git.
Reviews and run results are written to the outbox first and sent in order when the backend can be
reached, so nothing is lost offline; the server applies one sent twice once. run sends what is
waiting before it asks for a prompt, so a rejection always reaches the next one.
Claude Code, Codex, Antigravity CLI (agy) and Cursor's CLI (agent) are supported, found without any setup: on PATH, then where installers put
them when PATH misses it (the native installers' ~/.local/bin, npm's global folder, WinGet,
Scoop, Homebrew, Volta, Bun), then, for Claude Code, its older ~/.claude/local install and the
copy the Claude desktop app keeps, including the Windows package's own folder, and on Windows %LOCALAPPDATA%gyin and %LOCALAPPDATA%\cursor-agent. ROUGHBOOK_CLAUDE_BIN, ROUGHBOOK_CODEX_BIN, ROUGHBOOK_AGY_BIN or ROUGHBOOK_CURSOR_CLI_BIN names one anywhere else. Gemini CLI is not among them: Google replaced it with Antigravity CLI for personal accounts on 2026-06-18.
Cursor, VS Code (with Copilot), Antigravity and Windsurf run iterations with their own agents, with
you driving: run --agent cursor (or the local UI's Agent list) opens the repo and the run's prompt
in the editor, through its own launcher (cursor, code, antigravity, windsurf; on Windows the
editor's exe and cli.js that its .cmd launcher runs, with no shell), and puts the instruction on the clipboard for a new chat with the editor's agent: follow the prompt, and when the iteration is ready finish the run with the roughbook MCP tools. Each is found on PATH, in its
install folder (Windows), app bundle (macOS) or package folder (Linux), or through
ROUGHBOOK_<EDITOR>_BIN. --agent other opens the prompt with the OS's default app instead, and the
default app takes over when an editor cannot start. Editors never run unattended; Done, the checks
and the snapshot are the same as for a terminal agent. With no default set, run uses the first
terminal agent found, then the first editor.
MCP
roughbook mcp is an MCP server (stdio) with four tools, so an editor's agent finds a run and
finishes it itself: list_runs (the runs open in the repo), get_prompt, get_checks (what the
iteration must pass, checked now, without finishing anything) and finish_run (Done: the checks,
the files changed outside the run's folder, the screenshots and the report, with the warnings back
to the agent). It reads and writes local files only and reports through the same client and outbox
as done. It works out the repo from the folders the editor says it has open, else the folder it
was started in, else the repo path the agent passes. Unattended runs end when their agent exits,
so finish_run leaves them alone.
roughbook mcp install adds the server, as roughbook, at the user level, so every repo has it:
to ~/.cursor/mcp.json for Cursor; to VS Code's user mcp.json (%APPDATA%\Code\User,
~/Library/Application Support/Code/User or ~/.config/Code/User); to
~/.gemini/config/mcp_config.json for Antigravity (and its older ~/.gemini/antigravity/ file when
there is one); to ~/.codeium/windsurf/mcp_config.json for Windsurf; and through
claude mcp add --scope user and codex mcp add for Claude Code and Codex. Name any of cursor, vscode, antigravity, windsurf, claude-code and codex (cursor-cli and antigravity-cli mean the file their editor reads), or none for every one found here.
It keeps what else the file holds, leaves a file with comments alone and prints the entry to
paste, and --print only prints. The entry starts this Node with this install's cli.js, which
needs no shell or PATH on any OS (npx -y roughbook mcp when the studio runs from npx's cache);
run it again after moving Node. When ROUGHBOOK_CONFIG_DIR is set, the entry sets it too, so the
agents' server uses the same sign-in. A run's prompt ends by telling the agent to call get_checks and then finish_run with the run's id when it has the tools, to hand back to you when it does not, and that the app's dev server is yours to run, never its to start or stop, naming where the iteration renders (--dev-url, ROUGHBOOK_DEV_URL or http://localhost:3000); after an editor run, the studio says to install the server if that editor does not have it.
A terminal agent's run opens it in a terminal: Windows Terminal or a
console window on Windows, Terminal or iTerm on macOS (ROUGHBOOK_TERMINAL=iterm), and
$TERMINAL, x-terminal-emulator, GNOME Terminal, Konsole or xterm on Linux, where the window
stays open with the exit code until Enter when the agent fails; otherwise it prints
the command to paste. --headless runs the agent unattended, only with your own API key: ANTHROPIC_API_KEY for Claude Code, CODEX_API_KEY or OPENAI_API_KEY for Codex, GEMINI_API_KEY for Antigravity CLI (which takes it only with "modelProvider": "gemini" in ~/.gemini/antigravity-cli/settings.json), CURSOR_API_KEY for Cursor's CLI. The key, not the login, because that is what each vendor publishes: Anthropic keeps the Claude account sign-in for ordinary use of Claude Code itself and points products that drive it to API keys (legal and compliance); OpenAI says to use an API key for programmatic Codex CLI workflows (authentication); Cursor's CLI docs set one for scripts. A terminal or editor session is you driving your own tool, on whatever sign-in it has. Claude Code and Codex are confined to the run's folder by their own permissions; agy writes in the workspace and runs no commands; Cursor's CLI applies its changes with its shell in its sandbox; the studio's snapshot covers all four. Token counts are reported where the CLI gives them, which Cursor's does not. done takes desktop and phone screenshots
through the app's dev server (--dev-url, ROUGHBOOK_DEV_URL, or http://localhost:3000) with
Chrome, Chromium or Edge (ROUGHBOOK_BROWSER names another), into rough/.screenshots/. It asks
the preview route first and takes none, saying why, when nothing answers or the app answers with a
login (such as a pre-launch password), a redirect elsewhere, a 404 or an error; roughbook
screenshot takes them once that is fixed. What the studio itself writes while a run is open,
init's prep, a pull or a promotion, is never counted as the run's write outside its folder.
Pull
pull brings a web sketch into the repo as the starting point of a local iteration: a fork whose
parent is the sketch. Sketches use React, Tailwind and a few allowed packages (motion,
lucide-react, clsx, three, model-viewer), so the studio reads the sketch's files first, allocating
nothing, and decides how it comes in:
- Copied, where
rough/environment.mdsays a sketch renders here as it is (sketches: copy, as the Next.js recipe does), the iterations' entry is the sketch's, and the app'spackage.jsonlists every package the sketch imports and Tailwind. The backend allocates the next number and sends the files; the studio checks every path before writing (its own folder only, known file types, a few folders deep), never writes over an iteration, points the sketch's/api/files/…URLs at the local server, and regenerates the preview registry. The iteration is generated already, so it can be reviewed, refined (run --refine) and promoted like any other. - Ported everywhere else: a pull run, started like
roughbook run(--agent,--headless), whose agent rebuilds the sketch in the repo's own stack, with its components and tokens, keeping its layout, type, colour, copy and motion. The sketch's files are kept beside the component inrough/<page>/<component>/sketches/<sketch>/as a read-only reference no preview, registry or mirror reads.
When only packages the app does not list stand between the two, pull says which and asks before
adding them with the app's own package manager (--add says yes ahead, --port ports anyway);
nothing is added without a yes, and with nobody to answer the sketch is ported. The local UI's
Pull asks the same in a drawer. A run going at the time does not count the pulled files as its own
writes. Nothing local goes up but the repo's stack tokens, which stay with the iteration so
ratings compare ported work with copied work, stack by stack.
Promote
promote takes the component's selected iteration (or --iteration). The backend writes the
prompt from the brief; the studio copies the project and library assets the iteration uses into
public/rough/<page>/<component>/ (beside the app's package.json, the environment's assets
folder, or --public <folder>), the approved copy to rough/<page>/<component>/copy.md, fills in
the component file (src/components/<Name>.tsx by default on Next.js, the environment's promote
folder and file kinds on any other stack, or --to <file>), and opens your agent with the prompt.
What the prompt says of the stack comes from the environment and the recipe: what to read first,
how an asset is used (from the address the app serves its assets folder at, assets: public/images
@ /images, or as the file itself where the stack bundles it), the stack's own rules, and, with
copy: localized <file>, that the approved copy lands in that file, the one existing file the
agent may change. The file must be one of the environment's kinds, inside the repo, outside
rough/, the public folder and the folders the environment ignores. The server never hears the
file's path. An existing file is never overwritten, and nothing is committed: you review the diff.
The preview contract
Whatever the stack, a repo's preview setup must do six things, and roughbook init writes them down
for the agent in rough/CONTRACT.md: nothing from rough/ reaches production (the build's output
holds none of it, or the preview URL answers 404 in production mode); each iteration can be shown on
its own inside the app, with its preview.json passed in as its inputs when it has one; a rendered
iteration posts { roughbook: "ready", id } to the page's parent and a missing or failed one posts
{ roughbook: "error", id, message } (and 404 or 500 where the stack can); a change to an
iteration's files shows without a restart; the preview answers requests addressed to this machine
only; and every file the setup adds outside rough/ is listed in rough/environment.md.
Standalone mode
For a repo no live dev server suits (a plain HTML site, a PHP site with no Node.js tooling, a
design-system repo), mode: standalone makes the studio's own server the preview, so the setup
writes nothing outside rough/. It serves an iteration's HTML entry (index.html, a fragment or a
whole page) as it is, or bundles a React one (index.tsx, index.jsx) with esbuild against the
repo's own react and react-dom, at /rough-preview/<page>/<component>/<iteration>/, inside a
page that loads the site's stylesheets. public names the folder served at the site's root (.
for the repo itself), so the iteration's links to the site's files (/images/logo.svg) resolve as
on the site, while its own files are linked relative to its folder; stylesheets lists the
stylesheets as the site's pages link them, a path under that folder (/css/site.css) or a full
address. An HTML wrapper holds {{roughbook:iteration}} where the iteration goes; a React one is
a component that takes children. The fixture reaches an HTML iteration as
window.roughbookFixture and a React one as its props, and the variants reach the page's root
(lang, dir, a dark class with color-scheme: dark). The server answers 200, 404 for a key with
no iteration and 500 for one it cannot render (with the reason, such as esbuild's error), posts the
ready and error messages itself, and renders again on every request, so reload is studio.
It listens on 127.0.0.1 only: in the local UI on the studio's own port under the name localhost,
an origin apart from the UI's page at 127.0.0.1, so an iteration's code never reaches the UI or
its token; for doctor, screenshot and a run finished outside the UI, on a free port for the time
it takes. Every request carries the previews' own token (in the address, which the frame and the
screenshots get from the studio) unless the browser marks it as the preview page's own
(Sec-Fetch-Site: same-origin), which no other site can. The gate is about the site itself:
build: none with the folders it publishes as they are in buildOutput (none may hold rough/), or
gate: runtime with production serving the site, which must not hand out rough/.
Snapshot mode
For a stack whose previews are rendered images (Jetpack Compose with Roborazzi or Paparazzi,
SwiftUI snapshot tests, Flutter's widget tests), mode: snapshot has the repo's own snapshot
command render them, and a live preview through a mobile stack's web target (Expo, Flutter) may
name one too, for final review beside the page. The studio runs it with the keys of the iterations
whose files changed since their images were made ({keys} in the line, ROUGHBOOK_KEYS, and the
request file ROUGHBOOK_REQUEST names), every shot a key gets in ROUGHBOOK_SHOTS
(<name>:<width>x<height>:<variant>), where the stack sees the iterations in
ROUGHBOOK_ITERATIONS, and where the images go in ROUGHBOOK_OUT. The command writes
<out>/<key>/<shot>.png, the screenshots' own paths, so the frame, the canvas's cards and doctor
read them as screenshots, and says per key roughbook: ready <key>, roughbook: missing <key> or
roughbook: error <key>: <why> on a line of its own, or the same word in result.txt beside the
images where its test runner swallows what tests print; it exits 0 when every key rendered. A key it
says nothing of has rendered only if every image is new. One command runs at a time per repo; the
local UI renders the keys that changed together, five seconds after the last write; the frame shows
the images, Render again, and On the device where the environment names a device command.
placement: in-tree <path> is for a stack that compiles nothing outside its own source tree: the
studio mirrors each iteration folder of rough/ into <path>/<page>/<component>/<iteration>/
before every render (and writes a mark there, so it never empties a folder it did not make). The
path is a debug-only source set or target, gitignored (init adds it) and in the environment's
ignore list (the studio adds it), and the production build leaves it out, which doctor proves. On a
snapshot stack (assetsCopy: yes, the default in snapshot mode) the studio copies the asset files a
run's prompt names into the iteration's assets/ folder, since its tools load nothing over the
network.
The Expo recipe previews live through the app's web target (npx expo start --localhost): a route
under Expo Router that loads the iterations only under __DEV__, which the root layout lets through
in development. init makes the layout's own function a plain one and adds a default export that
calls useSegments() on every render and shows the route alone, or the layout as a component of its
own, so the layout's hooks keep their order on every route and the edit gives expo lint nothing to
report; uninstall puts the layout back as it was. rough/snapshots.mjs renders final-review snapshots on
an attached Android device or emulator (adb) or a booted iOS simulator, and opens a key there.
A WordPress theme keeps rough/ in its own folder, so a deploy of the theme can put it on the live
site. There it is inert: every PHP file the studio writes in rough/ starts with
<?php defined( 'ABSPATH' ) || exit; ?> (the agent's rules ask the same of its iterations), so the
web server runs nothing of it outside WordPress; rough/.htaccess refuses the folder on Apache and
LiteSpeed; init adds rough to the theme's .distignore, if it has one, for its release zip; and
doctor's runtime gate asks the production WordPress for the sample iteration's own files in the
theme's folder. On nginx, refuse the folder in the site's config or deploy the theme without it.
The Flutter recipe mirrors iterations into lib/rough/ with a registry of them, previews live
through the web target (flutter run -d web-server -t lib/rough_preview.dart), and renders snapshots
with a widget test (flutter test test/rough_snapshot_test.dart).
Recipes
A recipe is the studio's tested setup for one stack: init writes its preview setup without an
agent, setup --upgrade rewrites it, and roughbook new starts an app of the stack from an empty
folder with the stack's own scaffold. In the launch order:
| Recipe | Preview | Gate | roughbook new runs |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------- |
| next | The App Router route above | the build | create-next-app |
| vite (with React) | rough-preview.html at the root, which Vite serves in development and never builds, at ?key=<key> | the build (dist/) | create-vite, React and TypeScript |
| wordpress | A must-use plugin wp-env maps into WordPress in Docker (.wp-env.override.json), at /?rough_preview=<key>, in the local environment only | the runtime: the same WordPress in production mode | a block theme with wp-env |
| shopify | Snippets the studio writes per iteration and templates/search.rough-preview.liquid, through shopify theme dev on a development theme | the runtime: the store's live theme (production) | the Shopify CLI's theme init |
| laravel | routes/rough-preview.php, loaded from routes/web.php in the local environment only; the app runs with composer run dev | the runtime: APP_ENV=production php artisan serve | composer create-project laravel/laravel |
| expo | The Expo Router route above, on the web target; snapshots on a device | the build (expo export) | create-expo-app |
| flutter | The web target and widget-test snapshots above | the build (flutter build web) | flutter create |
| nuxt | A local module in modules/ that shows rough/preview-page.vue at /rough-preview/<key> under nuxt dev only | the build (.output/) | create-nuxt, its minimal template |
| swiftui | Snapshots by SwiftUI's ImageRenderer on a Mac, from the test target RoughSnapshotTests, declared in Package.swift only where its folder is | the release build | a Swift package with a SwiftUI library |
| compose | Snapshots by Roborazzi and Robolectric on the JVM, from a debug unit test, with the iterations mirrored into the debug source set | the release build (assembleRelease) | a Compose app, with gradle wrapper |
Each has a fixture in the conformance suite (below). Shopify's previews need a store you signed in to; SwiftUI's need a Mac.
Support tiers
What the site, the install page and these docs promise, from one list, src/recipes/support.json,
which the web carries byte for byte:
| Tier | Stacks | Means |
| ---------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Recipe | Next.js, Vite with React, WordPress themes, Laravel with Blade, Expo, Vue with Nuxt, Jetpack Compose | The conformance suite passes on Windows and Linux (macOS is not run yet) |
| Unverified | Shopify themes, Flutter, SwiftUI | The recipe is written and tested without a real run (no development store signed in, no Flutter SDK, no Mac), so new and init say so; doctor decides |
| Verified | Any other stack your coding agent can write | No recipe: the setup run's agent builds the preview setup, and roughbook doctor proves it on your repo |
Prerequisites per stack are the programs its commands and scaffold run, as doctor's step 0 looks for them: Git everywhere; Node.js for Next.js, Vite, WordPress (with Docker), Shopify (with the Shopify CLI), Laravel (with PHP 8 and Composer), Expo and Nuxt; the Flutter SDK; Xcode on a Mac for SwiftUI; a Java runtime for Compose (and Gradle to start a new app). Screenshots and doctor need Chrome, Chromium or Edge.
Doctor
roughbook doctor proves the contract on this repo, with the app's dev server running (with
--speed, doctor starts it itself when nothing answers, with the approved start command, and
stops it after). It first
checks the prerequisites (every program the environment's commands name is on the PATH, with how
to install each missing one) and that the environment reads and its commands are approved here;
then it writes two iterations of its own under a page of its own, rough/doctor-check-<id>/ (one
that renders, one that fails; a recipe's own, or the setup run's samples from rough/setup/sample/),
asks the dev server about them as the studio would, takes screenshots at every viewport and
variant, changes
the iteration and waits for the change to show, runs the production build and searches its output
for the iteration (or serves production and expects 404), tries the preview from this machine's
network address (and says so when it refuses only by the name a request gives, which anyone on the
network can send as localhost), checks that every setup file is in place and matches its template (and, after a
setup run, that nothing else outside rough/ changed), and takes its iterations away again. Each
check passes, fails with what to fix, or is skipped with the reason. The renders check tells three failures apart: the dev server never sent the iteration, the
app sent it but the page shows something else (a layout or wrapper that decides what to show by
path, and so must let the preview's address through in development), or the page shows it and
never posts ready; the checks that need the iteration on the page are skipped after the second.
The page's name is new on every run because a dev server's watcher (webpack's, on Windows) loses
a folder that was deleted and made again, and would miss the change to the iteration. The exit code
is 0 when every check passed, 1 when one failed, and 2 when the build was skipped (--skip-build),
which leaves the repo unverified. --json prints the report for a team's own CI. Every run writes
rough/.roughbook/doctor-report.json, the checks and the versions with no path and nothing from
the repo, which setup --fix reads and --report names for a support ticket. The outcome, the
mode, the family, the stack tokens and the ids of the checks that failed go to the server with the
link, so ratings and analytics can be read by stack; nothing else does.
In snapshot mode, or with --snapshots for a live repo's final-review command, doctor runs the
snapshot command for the two samples and a key with no iteration in one go, and expects images at
every shot (PNGs with the viewport's proportions, a dark one unlike the light), an error for the
broken sample and missing for the absent key; it changes the sample's fixture and expects other
images, renders with nothing changed and expects nothing rendered, then changes the sample and
expects it alone rendered again with other images; and builds for release with the sample mirrored
in place, searching the output inside app packages too (app.apk!classes.dex). Nothing is served,
so the loopback check is skipped.
In standalone mode doctor starts the studio's own server instead of asking the app, checks that the
site's folder and the stylesheets on it are there, and runs the same checks through it; the gate
searches the folders the site publishes for the sample iteration (build: none), or asks the site
in production mode for the sample's own file under rough/ and expects anything but that file
back: a 404, or a page of the site's own, as PHP's built-in server answers a missing file
(gate: runtime).
The twelfth check, --speed, measures the speed budgets of ROUGHBOOK-FRAMEWORKS-PLAN.md on a third
sample iteration, as a finished run leaves one: the preview visible (warmed once, then until it
shows and says ready) within 3 s; the cold start, from the start command to the first preview,
within 60 s when doctor started the app; and the screenshots at every viewport and variant within
15 s, taken once and not again when nothing changed. Each is said with its number and its budget.
The conformance suite is doctor over each recipe's fixture: npm run build, then
npm run conformance -- next copies conformance/fixtures/next to a temp folder, installs it,
prepares it as init would, starts its dev server, runs doctor and takes everything down;
--speed lets doctor start the dev server itself, so the cold start is measured too. How each
fixture is made, installed, served and taken down is in conformance/fixtures.json, and
npm run fixtures -- <recipe> writes it again from its pinned scaffold (--latest for the newest).
A snapshot recipe has no dev server, and doctor runs its snapshot command; Shopify's needs
SHOPIFY_FLAG_STORE naming a store you signed in to.
Speed
The budgets the plan sets are met the same way on every stack: the canvas shows still pictures
(each card's first screenshot) and only the open iteration and a comparison are live; a run's
preview is asked for once when the run finishes, so a dev server that compiles on first request
has it ready before you look; one headless browser is kept for the session and its tab reused for
every screenshot and probe (started on the first, closed after a few minutes idle or when the
command ends or is interrupted, its throwaway profile removed once it has exited; the profiles are
in roughbook-browsers in the OS temp folder, and each start removes those whose studio and
browser have both gone); frames load only while on screen, four at most; and screenshots are
taken once per iteration, at every viewport and variant, and again only when its files or the
environment's viewports changed (rough/.screenshots/<iteration>/shots.json records what they
were taken of; Take them again on the stage takes them anyway).
Layout
src/
cli.ts the only process.cwd(); finds the repo root; runs a command
commands/ one file per command, and the registry
api/ the typed client for /studio, its errors, and the outbox
config/ sign-in, server address, the user config folder
agents/ one adapter per coding agent: finding it, terminal sessions, unattended runs
launch/ everything that differs by OS: terminals, opening a browser, naming the OS
mcp/ the MCP server: the protocol, the four tools, and adding it to editors
screenshot/ one headless Chrome, Chromium or Edge kept for the session, at every viewport and variant
runs/ local runs: slots and prompt files, the checks, snapshots and revert, the watcher,
review decisions, sketch pulls, and promotion
repo/ the repo root, repo prep, the link file, command approvals, the setup templates
recipes/ everything that names a stack: the detection table, the ten recipes and their
scaffolds, tool hints, and the standalone server's renderers (HTML as it is, React
through esbuild)
snapshot/ snapshot mode: the command's contract, rendering the changed keys, the device command
environment/ rough/environment.md: its header's fields, read for this operating system
standalone/ the standalone server: the page around an iteration, its files, who may ask
doctor/ roughbook doctor: the contract, its checks (snapshot mode's apart), the sentinel search
fs/ whole-file JSON writes and the lock the CLI and local server share
server/ the local server: its API, the built-in terminal's sessions, who may ask
tests/
ui/ the local UI: Vite, React and @roughbook/ui, built into dist/ui