# Introduction

Bud is a companion for reviewing a web project with your team and your agents.

Bud runs next to your development server. It shows your live site page by page, puts the pages you choose side by side on a canvas, lets you browse and review your code, and keeps every conversation next to the work it's about. When you want, it hands a task to a coding agent in a sandbox and waits for your review.

- **Pages** – your configured routes beside a browser of the live site
- **Canvas** – every configured route as an always-current, full-page screenshot
- **Comments** – pin feedback to a point on a page or to lines of code
- **Tasks** – a board and task pages for the project's [Beads](https://github.com/gastownhall/beads) issues
- **Agents** – the [factory](/docs/app/guides/factory): a comment becomes a task, a coding agent finishes it in a sandbox, and nothing is pushed until someone approves
- **Files** – browse, edit, diff and stage without leaving the review
- **Storage** – tasks and discussions live in your project's Beads workspace, or in another store through an adapter

Bud shows each of these once your project has what it needs, and `bud doctor` says what the rest needs. For the whole team, [a hosted Bud](/docs/app/collaboration/hosting) runs on Cloudflare for a GitHub repository.

Bud doesn't replace your editor, framework or coding agent. It gives everyone the same view of what is being built, so the next change starts with a specific page, a precise detail and a decision, whether your own agent makes it or one Bud runs.

## How it works

```ts title="vite.config.ts"
import { defineConfig } from 'vite'
import { bud } from '@bud.sh/app/vite'

export default defineConfig({
  plugins: [bud()],
})
```

Bud starts with your Vite dev server and prints its URL. Your pages are served by your own app. Bud captures them in a headless browser and captures them again whenever they change, so the canvas follows your edits.

## Next steps

- [Install Bud](/docs/app/getting-started/installation) in a Vite project
- [Choose your routes](/docs/app/getting-started/configuration)
- [Leave your first comment](/docs/app/guides/comments)
- [Let agents finish tasks](/docs/app/guides/factory)

> [!NOTE] Free
> Bud installs from npm as one package, `@bud.sh/app`, with its Vite plugin and the `bud` command.


# Installation

Add Bud to a Vite project without changing how you develop it.

## Requirements

- Node 26.12 or newer
- Vite 8.3 or newer, serving your pages through its HTML pipeline
- A Git repository with `user.name` and `user.email` set
- Google Chrome, or Chromium from `npx playwright install chromium`, for the canvas's screenshots

## Install Bud

Bud is one package, `@bud.sh/app`: the app, its Vite plugin and the `bud` command. Install it as a development dependency:

```sh
npm install -D @bud.sh/app
```

## Add Bud to Vite

Add `bud()` next to your existing plugins:

```ts title="vite.config.ts"
import { defineConfig } from 'vite'
import { bud } from '@bud.sh/app/vite'

export default defineConfig({
  plugins: [bud()],
})
```

Create a config in the same directory:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'

export default defineConfig({
  name: 'My project',
  routes: ['/', '/pricing', '/blog/hello-world'],
})
```

## Start

Run your usual dev command, such as `npm run dev`. The terminal prints a **Bud** URL next to your app's URL. Open it, and edit a page to see it update on the canvas.

Bud only runs during development and adds nothing to your production build.

## Check what's set up

Bud shows only what your project has set up: without a task store there's no **Tasks**, without a discussion store no comments, and without a sandbox plugin no factory. To see what's there and what each missing piece needs, run the doctor in your project:

```sh
npx bud doctor
```

It lists tasks, comments, their sync and the factory, each with what it needs and a link to its guide.

> [!WARNING] Custom server rendering
> If your server renders HTML itself, it must pass it through `transformIndexHtml`. See [Vite and custom servers](/docs/app/getting-started/vite).

## What's next

- [Choose your routes](/docs/app/getting-started/configuration) and the viewports the canvas shows.
- Set up [tasks](/docs/app/guides/tasks) with Beads, so comments can become work.
- Turn on the [factory](/docs/app/guides/factory): coding agents finish tasks in sandboxes, and nothing is pushed until someone approves.
- [Host Bud on Cloudflare](/docs/app/collaboration/hosting) for your whole team, for a GitHub repository.


# Configuration

Choose the routes on your canvas, Bud's screens and how Bud connects to your app.

Bud reads exactly one `bud.config.ts` or `bud.config.js` from your Vite root. Changing it restarts Bud.

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'

export default defineConfig({
  name: 'Studio',
  routes: [
    '/',
    { id: 'collection', path: '/collection?view=grid', title: 'Collection' },
    { id: 'detail', path: '/collection/vase', title: 'Product' },
  ],
})
```

## Routes

Pick a few pages that represent your product, not every URL. Each route is a string or an object:

| Field     | Required | Description                                                |
| --------- | -------- | ---------------------------------------------------------- |
| `path`    | Yes      | A local path starting with `/`. Queries and hashes work.   |
| `title`   | Yes      | The label above the artboard.                              |
| `id`      | No       | A stable identity. Defaults to the path.                   |
| `pattern` | No       | The dynamic route the path renders, such as `/blog/:slug`. |
| `group`   | No       | A named group the artboard sits in on the canvas.          |

Use real URLs for dynamic routes: `/collection/vase`, not `/collection/:id`. A string route is titled by its path, and `/` is titled **Home**.

## Dynamic routes

Routes that share a `pattern` take one artboard. Its label gets a menu to switch between them, and only the chosen page loads:

```ts title="bud.config.ts"
export default defineConfig({
  routes: [
    { id: 'blog', path: '/blog', title: 'Blog' },
    { id: 'post-launch', path: '/blog/launch', title: 'Launch', pattern: '/blog/:slug' },
    { id: 'post-roadmap', path: '/blog/roadmap', title: 'Roadmap', pattern: '/blog/:slug' },
  ],
})
```

The artboard sits under the pattern's static part, so the canvas keeps its shape whichever page is open. The config is code, so you can also generate the list, for example from your content folder.

## Groups

The canvas arranges artboards by their paths: `/blog/launch` sits under `/blog`, joined by a line. Pages whose paths share no prefix, such as `/imprint` and `/privacy`, can still share a parent with `group`. Their own paths keep nesting inside it:

```ts title="bud.config.ts"
export default defineConfig({
  routes: [
    { id: 'imprint', path: '/imprint', title: 'Imprint', group: 'Legal' },
    { id: 'privacy', path: '/privacy', title: 'Privacy', group: 'Legal' },
    { id: 'terms', path: '/terms', title: 'Terms', group: 'Legal' },
    { id: 'terms-b2b', path: '/terms/b2b', title: 'Terms B2B', group: 'Legal' },
  ],
})
```

Canvas comments are stored with the route `id`. Give long-lived pages an explicit `id` before you leave feedback, so renaming a path doesn't orphan its comments.

## Flows

Some screens only exist by clicking through them, such as a booking: choosing a date, adding travellers, filling in their details. A flow pictures each of them. Your script drives the page from `start` and awaits `step(id)` at each screen; Bud captures the page there, at every viewport, and shows the steps as a group right after `/` on the canvas and in Pages:

```ts title="bud.config.ts"
export default defineConfig({
  routes: ['/', '/tickets'],
  flows: [
    {
      id: 'booking',
      title: 'Booking',
      start: '/tickets/day-pass/',
      script: './bud/flows/booking.ts',
      steps: [
        { id: 'offers', title: 'Offers' },
        { id: 'travellers', title: 'Travellers' },
        { id: 'details', title: 'Details' },
      ],
      // Stopped, and the walk fails, if it ever tries to send one.
      block: ['/reservations'],
    },
  ],
})
```

```ts title="bud/flows/booking.ts"
import type { Page } from 'playwright'

export async function walk({ page, step }: { page: Page; step(id: string): Promise<void> }) {
  await page.getByRole('button', { name: 'Book' }).click()
  await step('offers')
  await page.getByRole('button', { name: 'Add traveller' }).click()
  await step('travellers')
  await page.getByLabel('First name').fill('Anna')
  await step('details')
}
```

A flow never books anything: list the requests that would in `block`. A step the walk doesn't reach shows why, and a changed script pictures its steps again. Each step is a route with the id `<flow>/<step>`, so its comments, differences and pictures work like any page's.

## Screens

`screens` lists Bud's navigation in order. Leave it out to get Pages, Canvas, Tasks, Agents and Files; Tasks shows once a task store is set up, and Agents once the [factory](/docs/app/guides/factory) is. Leave a built-in out to turn it off, spread it to change its label or icon, and add screens of your own:

```ts title="bud.config.ts"
import { canvas, defineConfig, files, pages } from '@bud.sh/app'

export default defineConfig({
  routes: ['/'],
  screens: [
    pages(),
    canvas(),
    { ...files(), label: 'Code' },
    { id: 'metrics', label: 'Metrics', icon: 'chart', module: './bud/metrics.tsx' },
  ],
})
```

| Field    | Required | Description                                                            |
| -------- | -------- | ---------------------------------------------------------------------- |
| `id`     | Yes      | Lowercase letters, digits and hyphens, up to 32 characters.            |
| `label`  | Yes      | The name in the navigation and the page title.                         |
| `module` | Yes      | A `./` path to a `.ts`, `.tsx`, `.js` or `.jsx` file in your project.  |
| `path`   | No       | One segment, such as `/metrics`. Defaults to `/<id>`.                  |
| `icon`   | No       | A Bud icon, such as `chart`, `gauge` or `users`. Defaults to `layers`. |

Built-ins keep their own `id` and `path` (Pages at `/pages`, Canvas at `/canvas`; `/` opens the first screen), so links into them keep working. Bud checks every screen when it starts.

A screen module exports its screen as the default. `load` runs once while Bud shows its loader, and `render` gets the result:

```tsx title="bud/metrics.tsx"
import { Text, defineScreen } from '@bud.sh/app/screen'

export default defineScreen({
  load: async ({ project, signal }) => {
    let response = await fetch(new URL('/metrics.json', project.targetOrigin), { signal })
    return (await response.json()) as { visits: number }
  },
  render: (metrics) => <Text as="p">{metrics.visits} visits this week</Text>,
})
```

Screens run only in the browser, inside Bud, with the same access as Bud itself, so treat them like your config. They share Bud's `remix/component`, and `@bud.sh/app/screen` also exports Bud's `Button`, `Text`, `Loader`, `Spinner` and field cards. Screens can't import CSS yet. Bud bundles each screen with your project's Vite when it's first opened and again after you change it; reload to see an edit.

## Options

| Option                  | Default                     | Description                                                                              |
| ----------------------- | --------------------------- | ---------------------------------------------------------------------------------------- |
| `name`                  | `"Your project"`            | The project name shown in Bud.                                                           |
| `logo`                  | Bud's mark                  | Your mark in the navigation and on the tab. See [Logo](#logo).                           |
| `theme`                 | Bud's colors                | Your palette for light and dark. See [Theme](#theme).                                    |
| `locale`                | `"en"`                      | Bud's language: `"en"` or `"de"`.                                                        |
| `factory`               | `false`                     | The software factory's statuses; see [Factory](#factory).                                |
| `plugins`               | –                           | Stores, picker, task design, screens and sandboxes. See [Plugins](#plugins).             |
| `routes`                | –                           | The pages on the canvas. Required.                                                       |
| `screens`               | Pages, Canvas, Tasks, Files | Bud's navigation. See [Screens](#screens).                                               |
| `port`                  | `0`                         | Bud's own port, so its address stays the same. `0` picks a free one.                     |
| `target.origin`         | –                           | Your app's origin, such as `http://127.0.0.1:3000`.                                      |
| `target.port`           | –                           | Shorthand for `http://127.0.0.1:<port>`.                                                 |
| `storage`               | Both on                     | `false` turns tasks or discussions off. See [Storage](/docs/app/guides/storage).         |
| `designs.pages`         | `"path"`                    | How the Pages sidebar names pages: `"path"` alone, or `"title"` with the path beside it. |
| `comments.remote`       | `"origin"`                  | The Git remote for `gitComments()`.                                                      |
| `previews.scale`        | `2`                         | Screenshot pixel density, `1` to `3`. See [Canvas](/docs/app/guides/canvas).             |
| `previews.themes`       | `["light", "dark"]`         | Color schemes to capture, in toolbar order.                                              |
| `previews.viewports`    | One 1440px `Desktop`        | Screen sizes to capture. See [Viewports](#viewports).                                    |
| `previews.ready`        | –                           | What a page shows once it is ready. See [Ready pages](#ready-pages).                     |
| `previews.readyTimeout` | `60000`                     | How long a capture waits for `ready`, in milliseconds.                                   |
| `previews.css`          | –                           | CSS added to every captured page. See [Hiding overlays](#hiding-overlays).               |
| `files.edit`            | `true`                      | Set `false` to make Files view-only. See [Files](/docs/app/guides/files).                |
| `branches.main`         | `main`, else `master`       | The branch always captured. See [Branches](#branches).                                   |
| `branches.build`        | Your Vite dev server        | How a branch is installed, built and served. See [Branches](#branches).                  |

With Vite, leave `target` out: Bud uses the port Vite actually bound, including a fallback port. Set `port` when you open Bud from a bookmark or another device, so its address doesn't change on every restart.

Bud captures each route as a screenshot for the canvas. If your site has no dark style, capture only what it has:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'

export default defineConfig({
  routes: ['/', '/pricing'],
  previews: { themes: ['light'] },
})
```

## Viewports

Bud captures every route at one 1440px desktop width by default. List `previews.viewports` to capture other screen sizes instead, up to four, in toolbar order:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'

export default defineConfig({
  routes: ['/', '/pricing'],
  previews: {
    viewports: [
      { name: 'Desktop', width: 1440 },
      { name: 'Mobile', width: 390 },
    ],
  },
})
```

Each viewport has a `name` and a `width` from 240 to 3840 pixels. A viewport narrower than 800 pixels is captured as a phone, with touch and your page's viewport meta tag, and 844 pixels tall by default; set `mobile` and `height` to choose yourself. Listing viewports replaces the default, so leave `Desktop` out to capture only a phone.

With more than one viewport, the canvas toolbar has a button for each and one that shows them all side by side, each artboard labelled with its width. Comments stay on the viewport they were made on.

## Logo

`logo` shows your own mark in Bud's navigation and as its tab icon, instead of Bud's. Give an image path relative to the config, or a separate one for dark mode:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'

export default defineConfig({
  routes: ['/'],
  logo: { light: 'public/logo.svg', dark: 'public/logo-dark.svg' },
})
```

SVG, PNG, WebP, JPEG, GIF and ICO files up to 1 MiB work. The image must be inside your project, and it keeps its own colors in both themes.

## Theme

`theme` puts Bud in your palette. Give it your main colors, `background`, `foreground` and `primary`, for `light`, `dark` or both, and Bud derives the rest from them: surfaces, borders, hover and selection, muted text, scrollbars, tooltips, the text on your primary color, and Agent view's terminal and agent interface:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'

export default defineConfig({
  routes: ['/'],
  theme: {
    light: {
      background: 'hsl(0 0% 100%)',
      foreground: 'hsl(330 91% 13%)',
      primary: 'hsl(44 100% 49%)',
    },
    dark: {
      background: 'hsl(320 12% 7%)',
      foreground: 'hsl(340 18% 94%)',
      primary: 'hsl(44 100% 49%)',
    },
  },
})
```

Set any other token too, and it wins over the derived one. A `shell` of your own, such as a warmer one around the app, also tints the rest: controls, cards, hover, borders and scrollbars follow it instead of the background. A scheme without main colors keeps Bud's own for whatever you leave out.

Values are CSS colors or shadows. The tokens are `background`, `shell`, `main`, `canvas`, `panel`, `board`, `editor`, `editor-tabs`, `foreground`, `muted`, `primary`, `primary-hover`, `primary-foreground`, `border`, `tab-divider`, `hover`, `selected`, `control`, `control-active`, `control-selected`, `card`, `menu`, `menu-hover`, `card-frame`, `card-surface`, `card-border`, `card-frame-border`, `card-badge`, `overlay-shadow`, `tooltip`, `tooltip-border`, `tooltip-shadow`, `board-control`, `board-control-hover`, `board-card`, `board-segment`, `frame-control`, `scrollbar`, `scrollbar-hover`, `focus`, and the elevations `control-elevation`, `board-elevation`, `board-card-elevation`, `card-elevation`, `board-card-lift`, `modal-elevation`, `segment-elevation` and `control-open-elevation`. Agent view's terminal takes `terminal`, `terminal-foreground`, `terminal-cursor`, `terminal-selection` and the ANSI colors `terminal-black`, `terminal-red`, `terminal-green`, `terminal-yellow`, `terminal-blue`, `terminal-magenta`, `terminal-cyan` and `terminal-white`, each with a `terminal-bright-` form; the agent's interface builds its theme from those and `primary`, `muted` and `border`, in both schemes. A server hosting Bud can pass its own palette instead (`createNodeHost({ theme })`).

## Plugins

Everything you add to Bud is a plugin: where tasks and discussions live, how comments find elements, how tasks look, extra screens and where the software factory's agents work. List them in `plugins`, imported from `@bud.sh/app/plugins`:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'
import { local, reactGrab, taskDesign } from '@bud.sh/app/plugins'

export default defineConfig({
  routes: ['/'],
  factory: {},
  plugins: [reactGrab(), taskDesign('./bud/tasks.tsx'), local()],
})
```

| Plugin                   | Provides                                                                                        |
| ------------------------ | ----------------------------------------------------------------------------------------------- |
| `beads()`                | Tasks and discussions in Beads. Bud's default, so you only list it to pick one with `{ only }`. |
| `gitComments()`          | Discussions on the project's `bud/comments` branch. See [Storage](/docs/app/guides/storage).    |
| `memory()`               | Tasks and discussions in memory, for tests and examples.                                        |
| `react()`, `reactGrab()` | The React pickers. See [Picker](#picker).                                                       |
| `picker('./picker.ts')`  | Your own picker. See [Pickers](/docs/app/guides/pickers).                                       |
| `taskDesign('./x.tsx')`  | Your task design. See [Task designs](/docs/app/guides/task-designs).                            |
| `local()`, `boat()`      | The factory's sandboxes. See the [Factory guide](/docs/app/guides/factory).                     |
| `opencode()`             | The factory's agent and its model; OpenCode on a free model by default.                         |

Only one plugin may provide each store, the picker, the task design and the sandboxes. Your own plugins use `definePlugin` from the same entry: an object with a `name`, the parts it provides (`work`, `threads`, `picker`, `taskDesign`, `screens`, `sandbox`, `harness`) and, as in Vite, an optional `config(config)` hook whose returned settings Bud merges in before it checks the config. `enforce: 'pre'` or `'post'` runs a hook before or after the others.

## Language

`locale` sets the language of Bud's interface: English (`"en"`, the default) or German (`"de"`). A server hosting Bud can choose it for each person instead, with `fetch(request, { locale })`.

## Factory

The software factory turns comments into tasks that coding agents finish in sandboxes; the [Factory guide](/docs/app/guides/factory) covers the flow, its options and security. Its steps are statuses in your task store, so the board has a column for each:

| Step           | Status           | Kind        |
| -------------- | ---------------- | ----------- |
| Open           | `open`           | not started |
| In progress    | `in_progress`    | started     |
| Needs approval | `needs_approval` | started     |
| Ready to merge | `ready_to_merge` | started     |
| Merged         | `merged`         | done        |

```ts
factory: {},
```

turns it on with these Beads names; agents work in the sandboxes of a sandbox plugin, `local()` or `boat()`. If your task store calls them something else, map them:

```ts
factory: { statuses: { needsApproval: 'review', merged: 'shipped' } },
```

When the task store lacks some of them, Bud adds them itself: with Beads, that's `bd config set status.custom`, merged with the custom statuses you already have, which stay as they are. A status that exists as another kind, such as a `merged` that isn't done, has to be fixed in the store or mapped to another name; until then the factory stays off, and `bud doctor` says which status is in the way.

The factory's other options, such as the sandbox's `setup` and `dev` server, the agent's `instructions` and `herdr: true` for live agent status, are in the [Factory guide](/docs/app/guides/factory#turning-it-on).

## Hiding overlays

Development tools that draw over your site, such as React Grab, show up in screenshots too. `previews.css` adds CSS to every page Bud captures, and never to your site in your own browser:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'

export default defineConfig({
  routes: ['/', '/pricing'],
  previews: { css: '[data-react-grab] { display: none !important }' },
})
```

Changing it takes every picture again.

## Picker

Comments on the canvas and in Pages remember the element they point at, not only a point. The picker finds it:

| Plugin                  | Finds                                                                                   |
| ----------------------- | --------------------------------------------------------------------------------------- |
| None (default)          | The element, the card or section around it and its text, from the page's DOM.           |
| `react()`               | The same, plus the React component and its source line, from React's development build. |
| `reactGrab()`           | The same as `react()`, picking in Pages with your project's React Grab.                 |
| `picker('./picker.ts')` | Your own browser module in the project. See [Pickers](/docs/app/guides/pickers).        |

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'
import { reactGrab } from '@bud.sh/app/plugins'

export default defineConfig({
  routes: ['/', '/collection'],
  plugins: [reactGrab()],
  previews: { css: '[data-react-grab] { display: none !important }' },
})
```

`reactGrab()` needs `react-grab` installed in your project and loaded in development. Pickers run only in your pages during development, never in your build.

## Branches

The canvas keeps screenshots of `main` beside your working tree, and of every branch you activate in its sidebar. When one of them gets a new commit, Bud builds that commit in a folder outside your checkout and captures every route again; your working tree, staging area and branches never change. The folder is a Git checkout of the commit, so builds that read `HEAD` or the log work as usual. By default a build uses your project's `node_modules` (and those of its workspace packages) and runs your Vite dev server. Projects that build or serve differently give the shell commands:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'

export default defineConfig({
  routes: ['/', '/pricing'],
  branches: {
    main: 'main',
    build: {
      install: 'npm ci',
      serve: 'npx vite --host 127.0.0.1 --port $PORT --strictPort',
    },
  },
})
```

`serve` must listen on `127.0.0.1:$PORT`. Bud's Vite plugin stays off in branch builds, and a branch's build stops once its screenshots are taken. See [Canvas](/docs/app/guides/canvas#branches).

## Ready pages

Bud captures a page once its requests have settled and everything it shows has loaded, including images and sections that appear only when scrolled to. A page that renders after hydration or after fetching its data should also say when it is ready, so the canvas never shows a loading state. Point `previews.ready` at a CSS selector that matches only then, or at a JavaScript expression that becomes true:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'

export default defineConfig({
  routes: [
    '/',
    { path: '/map', title: 'Map', ready: '[data-map-ready="true"]' },
    { path: '/embed', title: 'Embed', ready: false },
  ],
  previews: { ready: '#root[data-client-ready]' },
})
```

A route's own `ready` replaces the shared one, and `false` turns it off. Bud waits up to `previews.readyTimeout` before every capture. A page that never gets ready is still captured, and its artboard shows a warning naming the condition.

## JavaScript

`bud.config.js` takes the same shape. The file is loaded by Node, not Vite, so path aliases don't apply and relative imports need extensions.


# Vite and custom servers

How Bud connects to your pages, and what a custom server needs to do.

Bud waits for Vite's HTTP server and starts on its own port. Bud opens your pages in a headless browser and captures them; Vite's hot updates reach those pages like any other tab, so the canvas follows your edits. Your pages never receive Bud's API capability.

## Custom server rendering

If your server renders HTML itself, pass every document through `transformIndexHtml` with the original URL, so Vite's client and its hot updates reach the page:

```ts title="render.ts"
import type { ViteDevServer } from 'vite'

export async function render(vite: ViteDevServer, url: string, template: string, html: string) {
  let shell = await vite.transformIndexHtml(url, template)
  return shell.replace('<!--app-html-->', () => html)
}
```

Keep a real Vite HTTP server. Middleware mode without a listener isn't supported, and streamed HTML needs its own integration.

## Without Vite

For an app you run separately, set its origin and start Bud with the CLI:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'

export default defineConfig({
  routes: ['/', '/pricing'],
  target: { origin: 'http://127.0.0.1:3000' },
})
```

```sh
npx bud .
```

Without Vite, Bud reloads the captured pages whenever project files change.


# Pages

Browse your live site inside Bud, page by page, in tabs.

**Pages** is where Bud opens, at `/pages`. The sidebar lists every route in your [configuration](/docs/app/getting-started/configuration); the browser beside it shows the real, live page, with hot updates and everything you can click.

- Click a page to open it in the current browser tab. Hold Ctrl/⌘ to open it in a new one.
- The browser has its own tabs, back, forward, reload, home and an address bar. The address bar stays on your site.
- Each tab at the top of Bud keeps its own browser tabs and history, and the page you are on is part of the URL (`/pages/docs` shows `/docs`), so Bud's back and forward follow the pages you visited and you can link straight to a page.

## Branches and pictures

Once a branch has [captured pictures](/docs/app/guides/canvas), the sidebar lists the pages once per section: the working tree, which is your live site, then each captured branch, main first. A page opened from a branch's section shows that branch's picture at the viewport picked in the toolbar, without browser controls. A hosted Bud has no running site, so every section shows pictures there.

Choose the compare button to show a page's pictures from two branches side by side, and pick each one from the selects beside it. Comments work while comparing too: each belongs to the branch whose picture you click.

Set `designs.pages` in your [configuration](/docs/app/getting-started/configuration) to name pages by their path, the default, or by their title with the path beside it.

A [flow](/docs/app/getting-started/configuration#flows)'s steps sit in a group of their own right after `/`, named by their titles. They only exist as pictures, so even beside your live site a step opens as its picture.

## How it follows your pages

During development, Bud adds a small script to your pages. Inside Bud's browser it reports which page is showing and its title, carries out back, forward and reload, and runs your [picker](/docs/app/guides/pickers) when you [comment on an element](/docs/app/guides/comments#in-pages). Outside a frame it does nothing, so your site and Bud's screenshots are unaffected.

Your pages load in a sandboxed frame on your site's own address, never Bud's. They receive only those requests from Bud, never its data or access. If your site is served from the same address as Bud, Pages can't show it; serve it on its own port instead.

For screenshots of every page side by side, use the [Canvas](/docs/app/guides/canvas) at `/canvas`.


# Canvas

See every page side by side as up-to-date screenshots, and zoom from overview to detail.

Every configured route is an artboard showing a full-page screenshot of your actual app, sharp enough to read at any zoom. All canvas tools live in the toolbar at the top.

| Tool             | Use it to                                     |
| ---------------- | --------------------------------------------- |
| **Pan**          | Move around the canvas.                       |
| **Light / Dark** | Show every page in its other color scheme.    |
| **Top only**     | Show only the top of each page, or all of it. |
| **Comment**      | Place a pin and start a conversation.         |

The refresh button beside an artboard's label captures that page again, and spins while the page is being updated. To use a page, open it in your browser.

Screenshots are plain images, and the canvas draws only what you can see: zoomed out, each page is a small picture of itself; zoomed in, only the parts of a page on screen load at full size. A site with hundreds of long pages stays as quick to move around as a small one.

## How pages stay current

Bud opens each route in a headless Chromium at 1440px wide, or at each of your [viewports](/docs/app/getting-started/configuration#viewports), once per color scheme, with reduced motion so animations don't change the picture. Whenever a page changes, Bud fingerprints what it renders, its markup and styles, and only takes a new screenshot when the fingerprint is new:

- **With Vite**, its hot updates reach those pages like any other tab, so the canvas follows your edits within a moment. The refresh icon of each page Vite updates spins until its new screenshot lands, or until Bud finds nothing changed.
- **With the CLI**, Bud reloads the pages when project files change.

Bud keeps up to 10 pages open at once, counting each color scheme and viewport, and loads and captures only a couple at a time, so a large site doesn't slow your machine down. On a larger canvas every page is still captured in turn, and the pages beyond that keep their last screenshot until you press their refresh button.

Screenshots are kept by fingerprint in `node_modules/.cache/bud/previews`, and your browser keeps the ones it has shown. Undo an edit and the page shows its earlier screenshot at once; reload the canvas and it paints straight away; restart Bud and unchanged pages appear without being captured again.

While a page is captured for the first time, its artboard shows a loader. If it can't be captured, the artboard says why, with **Try again** and **Open page**. Bud uses the Chromium installed for Playwright, or your installed Google Chrome; without either, run `npx playwright install chromium`.

Set the screenshot density, color schemes and screen sizes in your [configuration](/docs/app/getting-started/configuration) with `previews.scale`, `previews.themes` and `previews.viewports`.

## Flows

Screens only clicking reaches, such as a booking, show as [flows](/docs/app/getting-started/configuration#flows): your script walks the flow and Bud pictures every step, at every viewport. A flow's steps form one group right after `/`, in order, each labelled with its step's title. Each walk starts fresh from the flow's start page; a step it didn't reach says why. Comments, differences and the slideshow work on steps as on any page.

## Viewports

With more than one viewport configured, such as desktop and mobile, the toolbar has a button for each and one that shows them all. Showing all puts every page's viewports side by side, each artboard labelled with its width. Comments belong to the viewport they were made on, so a comment on the phone layout only shows on phone artboards. Differences compare each viewport on its own.

## Branches

The sidebar beside the canvas lists your **working tree**, which follows your edits live as above, then `main` and the branches you activated, then the repository's other branches. Choose one to see its screenshots; the canvas keeps your view.

- `main` is always captured. Activate any other branch with its **+** button, and stop with the eye button.
- When a branch gets a new commit, Bud builds that commit outside your checkout, captures every route, and replaces the branch's screenshots. The sidebar shows the commit they show, or what is happening: waiting, building, capturing with a count, or why a capture failed.
- Branch screenshots keep working after their build stops: every screenshot is saved with a map of the elements it shows, so comments on a branch find the element under the click, and pins follow their elements, without the branch running.

Set the main branch and how branches are built in your [configuration](/docs/app/getting-started/configuration#branches).

## Differences

The compare button in the toolbar shows only what differs between the screenshots you're looking at and another set: `main` by default, or the working tree while you look at `main`. Pick any other captured set from the **Compare with** menu.

- Each page that changed shows as two artboards side by side, the other set on the left and yours on the right, made of just the parts that differ, with the changed columns outlined. Pages that didn't change are left out, and the status line counts them.
- Sections one side adds or removes show on their own; the content they push down isn't flagged as changed.
- The play button steps through every change, one at a time, at full width. Use ← and → to move and Esc to close.
- The comparison runs in your browser. Bud compares the saved screenshots, so neither set's build has to be running. Comments are off while comparing.

## Moving around

Drag or scroll to pan. Hold Ctrl/⌘ while scrolling, or pinch, to zoom around the pointer. **Fit** brings every route back into view.

With the canvas focused:

| Key              | Action         |
| ---------------- | -------------- |
| `+` / `-`        | Zoom in or out |
| `0`, `F`, `Home` | Fit all routes |
| Arrow keys       | Pan            |

Bud remembers the view per project in your browser. It isn't part of your comments.


# Comments

Pin a conversation to a page or to lines of code, and move it to a decision.

Comments need a storage adapter. By default that's the project's [Beads](https://github.com/gastownhall/beads) workspace, the same one **Tasks** uses, so run `bd init` once if the project doesn't have one. Without a ready adapter there are no comments anywhere: no comment tools on the canvas or in Pages, no comments sidebar and no discussions on tasks. `bud doctor` says what's missing, and Bud never saves comments somewhere else. See [Storage](/docs/app/guides/storage).

## On a page

Choose **Comment** in the toolbar and click the detail you mean. Write what should change, not just what feels off:

> Give this headline more room above the collection. Keep the same spacing on `/collection`.

A comment remembers the element under it as well as the point: the element itself, the card, list item or section around it, and, with a framework [picker](/docs/app/guides/pickers), the component and its source line. A thread that knows its source links to that line in **Files**.

Every screenshot is saved with a map of the elements it shows, so a click names the element that was under it even after the page has changed, on your working tree or on a [branch](/docs/app/guides/canvas#branches) whose build has stopped. Each screenshot finds the commented elements again, so pins move with what they point at when the layout changes. When the element is gone and nothing in its place is the same block, the pin stays where it was, dashed, and its label says the element is no longer on the page. Reply with the new context or resolve it.

## In Pages

Choose the crosshair in the **Pages** toolbar, then click an element of the live page; Esc cancels. With `reactGrab()` you pick with React Grab's own overlay. Bud opens a comment beside the spot, naming the element and its source. It belongs to the route's artboard on the canvas, pinned where the canvas shows that element, so only pages in your routes take comments. On a picture, the comment keeps the point you clicked, on the branch whose picture it is.

## On code

In **Files**, select lines and press Ctrl/⌘+Alt+M. Code comments keep the exact text they were made on. When the code changes, Bud follows it where it can and marks the comment **outdated** where it can't, showing the original lines.

## Replies and status

Reply to a thread or to a single message. Shift+Enter sends, as does Ctrl/⌘+Enter, and the send button spins until the comment is saved. A reply or status change shows at once, under "You" until it's saved; if it can't be saved, Bud takes it back and keeps your text. Replies from teammates' tabs and agents appear in open threads by themselves.

| Status             | Meaning                          |
| ------------------ | -------------------------------- |
| **Without status** | New feedback.                    |
| **In progress**    | Someone is working on it.        |
| **Needs approval** | Ready for another look.          |
| **Resolved**       | Done. The pin leaves the canvas. |

The sidebar searches messages, authors and paths, and filters by status. Resolved threads stay searchable. **Copy link** gives every thread its own address, `/comments/<id>`, which opens it on its page or at its lines of code.

## Deleting

Your own comments have a **…** menu with **Delete comment**; Bud asks once more before it deletes. Replies to it stay and move up to the message it answered. A thread's first comment can be deleted only while nobody has replied, and deleting it deletes the thread. Deleted comments disappear from Bud but stay in your Beads history, and a task's own first comment always stays. Only the comment's author sees the menu, and the server checks the author again.

## Linking tasks

**Make task** in a placed thread's toolbar turns the thread into a task and opens it. The task's title is the first line of the comment; its description quotes the comment and says where it was made, the element and its source line, and links back to the thread. The thread is linked to the new task, so it shows there too.

A discussion can belong to several tasks. Use **Link tasks** in a thread or in a task's **Discussion** to pick them; the thread then shows on every linked task, with the others listed. This is how a pin on a page becomes tracked work.

## Authors

Bud reads `user.name` and `user.email` from Git on the server for every message. There are no accounts, and a page can't choose an author.

```sh
git config --local user.name "Your name"
git config --local user.email "you@example.com"
```

Comments save to your storage adapter. To share them with Beads, [sync your workspace](/docs/app/collaboration/sharing).


# Files

Browse, edit and review your project's code next to its pages.

**Files** works like a code editor: the explorer on the left shows your project tree with Git status, and every file you open gets a tab. Close a tab with its × or a middle click; a file with an unsaved draft keeps its tab, marked with a dot, until you save or discard it. The bar above the code holds the file's path and actions, and the status bar below shows the branch, the selected lines and the file's encoding. The tree and its Git badges follow changes made outside Bud, and a reload shows the tree your browser last saw while Bud reads the latest.

To keep a project read-only, turn editing off. Files then only views code; Git review, staging and conflict resolution still work:

```ts title="bud.config.ts"
export default defineConfig({
  routes: ['/'],
  files: { edit: false },
})
```

## Editing

Edits are drafts until you save with Ctrl/⌘+S. A save only succeeds if the file on disk is still the version you opened; if something else changed it, Bud keeps your draft and shows the conflict instead of overwriting.

| Shortcut       | Action               |
| -------------- | -------------------- |
| Ctrl/⌘+S       | Save                 |
| Ctrl/⌘+G       | Go to line           |
| Ctrl/⌘+Shift+P | Editor commands      |
| Ctrl/⌘+Alt+M   | Comment on selection |

New files, folders, renames and deletes are previewed before they apply. Deleted and moved originals stay recoverable.

## Review

Compare the working file with the staged version, `HEAD` or any commit, and step through its history. Stage or unstage whole files, hunks or selected lines. Merge conflicts open in a resolver with both sides and the base.

Review actions never change branches or move refs, and every write is checked against the revision you were looking at.

## Search

Search file names with Ctrl/⌘+P. Ctrl/⌘+Shift+F opens project search in the sidebar, with literal or regular-expression queries and include/exclude paths; its results stay beside the file you open from them.


# Tasks

Plan the work on a board backed by your project's Beads issues.

**Tasks** shows the issues of a [Beads](https://github.com/gastownhall/beads) workspace in your project. Bud doesn't have its own task storage; the built-in adapter reads and writes through the `bd` CLI, and [another adapter](/docs/app/guides/storage) can supply tasks from a different tracker.

## Requirements

- `bd` 1.3.1 or newer on your `PATH`
- An initialized workspace (`bd init`) in the project

Without them there's no **Tasks** screen at all, and no task links or **New task** anywhere; `bud doctor` says what's missing. Bud never initializes a workspace for you.

## The board

Tasks sit in status columns, or in a list. With the [factory](/docs/app/guides/factory) on, the columns are its steps: Open, In progress, Needs approval, Ready to merge and Merged, with Closed folded away. Without it, the board shows open and started work, with done work folded away.

Each card shows the task's title, its agent's run, the person who created it and when, its epic and how many messages its discussion has. The top bar holds a search, a person filter (everyone, **Mine** or one person), an epic filter once there are epics, the board or list switch and **New task**. On a phone or tablet, the search opens from its button and the filters share one menu.

You move a card by hand only to Open or to Closed. Every other step belongs to whoever does the work: in factory mode, the factory moves the task as its agent works.

**Sync** pulls and pushes through the Beads remote, and shows only when the workspace has one.

The board stays current on its own. Changes made with the `bd` CLI, by an agent or in another tab appear within a few seconds, and an open task page picks them up too. Your browser keeps the last board it saw, so reopening **Tasks** shows it at once while Bud reads the latest.

## Task pages

Open a task to read it on its own page, at `/tasks/<id>`. The breadcrumb shows its epic, and **Back** returns to the board where you left it.

The page leads with the title and description, which you edit in place, then a line with the task's status, the person who created it and when. Below come its epic, the tasks linked to it, the agent's run when the factory is on, the discussion, and its activity, folded away until you open it. The menu beside the title closes or reopens the task, or deletes it after asking.

**New task** asks only for a title and a description. Bud records who created each task on the server: your Git author locally, the person who signed in on a hosted Bud. Nothing the browser sends can change it.

Changes show right away, on the board too. Every change still checks that the task hasn't changed since you opened it; if Beads refuses one, Bud undoes it and tells you why. Bud never retries a change on its own.

Beads keeps more than this screen shows, such as priority, type, assignee and labels. To show them, see [Task designs](/docs/app/guides/task-designs).


# Task designs

Change how tasks look while keeping the same storage and operations.

A task design is a set of Remix components. Replace one part of the board or detail page and keep the rest of Bud's built-in interface. Storage stays independent: a design renders the fields, statuses and actions declared by your work adapter.

## Start with one card

Create a browser module inside your project:

```tsx title="designs/compact-card.tsx"
import { defineTaskDesign, TaskCardDefault } from '@bud.sh/app/design'
import type { TaskCardDesignProps } from '@bud.sh/app/design'
import type { Handle, RemixNode } from 'remix/component'

function CompactCard(handle: Handle<TaskCardDesignProps>): () => RemixNode {
  return () => <TaskCardDefault {...handle.props} compact />
}

export default defineTaskDesign({ card: CompactCard })
```

Add it with `taskDesign()`, separately from your storage:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'
import { taskDesign } from '@bud.sh/app/plugins'

export default defineConfig({
  routes: ['/'],
  plugins: [taskDesign('./designs/compact-card.tsx')],
})
```

The card keeps Bud's links, keyboard focus and drag behavior. Columns, dialogs and the task page retain their defaults. Leave `taskDesign()` out to use the built-in design throughout.

## Bring back more fields

Bud's built-in tasks screen is deliberately small: a title, a description, who created the task, its epic, linked tasks, the agent's run, the discussion and activity. Beads records much more, such as priority and assignee, and a design can show it. A `properties` slot puts a card beside the task's page:

```tsx title="designs/properties.tsx"
import { defineTaskDesign } from '@bud.sh/app/design'

export default defineTaskDesign({
  properties: (handle) => () => (
    <dl class="grid grid-cols-[6rem_1fr] gap-2">
      <dt>Priority</dt>
      <dd>{handle.props.renderField('priority')}</dd>
      <dt>Assignee</dt>
      <dd>{handle.props.renderField('assignee')}</dd>
    </dl>
  ),
})
```

`priority` and `assignee` are the Beads adapter's field IDs; another adapter may use other IDs, which `fieldByRole` finds by role. An `actions` slot adds controls under the properties, such as a button that runs one of the schema's `actions` through `handle.props.operations.act`.

The marketing consumer includes this example at `apps/web-marketing/designs/compact-card.tsx`, disabled by default.

## Available slots

Every slot is a Remix component. Its setup runs once; read changing data from `handle.props` inside the returned render function.

| Slot         | What it replaces                                                  | Built-in component |
| ------------ | ----------------------------------------------------------------- | ------------------ |
| `card`       | A board card or list row                                          | `TaskCardDefault`  |
| `page`       | The complete task detail page, including loading and error states | `TaskPageDefault`  |
| `properties` | A card beside the page, empty by default                          | None               |
| `actions`    | Controls under the properties, empty by default                   | None               |
| `sections`   | The sections below the discussion, by default Activity            | `SectionsDefault`  |
| `column`     | A status column's heading and cards                               | `ColumnDefault`    |

All defaults are exported from `@bud.sh/app/design`. Render one inside your own component to compose it. A custom page can render `<TaskPageDefault {...handle.props} />` and retain any separately configured properties, actions or section slots.

The workspace keeps the column's drop target. A column receives its `status`, `items` and rendered card `children`; rendering those children preserves your selected card design.

## Data and field rendering

Slots receive `schema`, optional `operations`, `renderField`, `itemHref`, `onSelect` and a `project` display identity containing only `id` and `name`. Item slots receive the current `item`. A page's item is `null` while loading or after a failed read; columns receive `items` instead.

Cards also receive selection, completion, compact layout, drag and link props. Pages receive the loaded detail, snapshot, discussion slot, loading state, errors, refresh callback and editor drafts. The exported `TaskCardDesignProps`, `TaskPageDesignProps` and other slot types describe the complete interface.

Use schema roles when placing content; an adapter chooses its own field IDs. For example:

```tsx
import { fieldByRole } from '@bud.sh/app/design'

let title = fieldByRole(handle.props.schema, 'title')
let content = title ? handle.props.renderField(title) : null
```

`renderField(fieldOrId, value?)` uses the shared renderers for text, dates, people, labels, markdown, links and JSON. It reads the item's current value unless you supply one. Plain strings and metadata remain escaped.

## Keep operations explicit

Designs receive consumer-owned callbacks rather than storage services. Keep controls conditional on the schema's capabilities and the available operations. Use the revision from the item when saving; preserve a draft after a conflict or uncertain result and let the user review it.

The default editor, menu and sections already handle these cases. Reusing them also retains lazy section loading, cancellation, focus restoration and discussion behavior.

## Module loading

Design modules run only in Bud's browser interface. They must remain inside your project, outside `node_modules`, and cannot be server modules. Bud shares its Remix runtime through the same import map used by custom screens.

Use Bud's components from `@bud.sh/app/screen`, the design exports, theme tokens or inline styles. Custom modules cannot import CSS. Bud rebuilds installed modules when a local input changes; reload the task screen to load that build. Workspace source development uses Vite's module pipeline.

A missing or unsafe module path prevents startup. A build failure or invalid export appears in Tasks with a retry action. Bud never silently substitutes the default design for a broken configured module.


# Storage

Choose where tasks and discussions are stored. Bud is the interface, not the database.

Bud doesn't store tasks or comments itself. A **work adapter** supplies tasks and a **thread adapter** supplies discussions: canvas pins, code comments and task discussions. The built-in Beads adapter supplies both, so with no configuration everything lives in your project's Beads workspace.

Plugins choose other stores. This keeps tasks in Beads and discussions on Git:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'
import { beads, gitComments } from '@bud.sh/app/plugins'

export default defineConfig({
  routes: ['/'],
  plugins: [beads({ only: 'work' }), gitComments()],
})
```

| Plugin          | Stores                                                 |
| --------------- | ------------------------------------------------------ |
| `beads()`       | Tasks and discussions in the project's Beads workspace |
| `gitComments()` | Discussions on the `bud/comments` Git branch           |
| `memory()`      | Tasks and discussions in memory, gone when Bud stops   |
| Your own        | See [Your own store](#your-own-store)                  |

Only one plugin may store tasks and one discussions. `storage: { work: false }` or `{ threads: false }` turns one off, and Bud never falls back to another store.

An adapter declares its fields, statuses, actions and extra sections, and Bud's interface follows: a different tracker shows its own properties in the same task pages.

## Your own store

A store is a plugin whose `work` or `threads` is an Effect layer providing `WorkAdapter` or `ThreadAdapter` from `@bud.sh/app/server`, or a function making one for the project. It runs only in Bud's server, so credentials come from environment variables and never reach the browser. Bud checks the services when it starts.

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'
import { definePlugin, memory } from '@bud.sh/app/plugins'

// A tracker of your own would build its layer from WorkAdapter in @bud.sh/app/server.
const tracker = memory().work

export default defineConfig({
  routes: ['/'],
  plugins: [definePlugin({ name: 'tracker', label: 'Tracker', work: tracker })],
})
```

## Comments in Beads

Each discussion is a Beads `message` issue: hidden from `bd list` and `bd ready`, so agents' work queues stay clean, and synced with the rest of the workspace. Replies are Beads comments, resolving closes the issue and linked tasks are `related` dependencies. You can delete your own messages; a discussion's first message goes only while it's the only one, and deleting it removes the discussion.

## Moving older comments

Projects that used Bud's earlier Git storage can copy their comments into the configured discussion store:

```sh
npx bud comments migrate --dry-run
npx bud comments migrate
```

Migration keeps authors, times, statuses and anchors, skips threads it already copied and never changes the `bud/comments` branch. Keep `gitComments()` in `plugins` if you'd rather stay on Git.


# Pickers

How comments find the element they point at, and how to bring your own picker.

A picker runs in your pages during development and answers three questions for Bud: what is at this point, where is this element now, and which element does the person choose. Bud's screenshot tabs ask it when you comment on the canvas and whenever a page changes; Pages asks it when you pick an element. Choose one with a plugin in your [configuration](/docs/app/getting-started/configuration#picker).

## What a comment remembers

| Part    | Holds                                                                                           |
| ------- | ----------------------------------------------------------------------------------------------- |
| Element | A selector path, its tag, text and a fingerprint of both, and where the point sits inside it.   |
| Block   | The landmark around it that outlives its details: a card, list item, section or component root. |
| Source  | With a framework picker: the component, file, line and column that rendered it.                 |
| Picker  | Which picker described it.                                                                      |

To find an element again, Bud tries its path, then the same tag and text anywhere on the page (or inside its block), then the block itself when its label still matches. Anything else counts as gone, and the pin is marked detached rather than moved onto something else.

## React

`react()` reads the component and its source line from React's development build and your build's source maps, inline or beside each file. It runs in pages without your dev server too, such as a branch's build, where Bud adds the picker itself. Production builds keep no component information, so their comments name only the page's elements.

## React Grab

`reactGrab()` does the same, and picks in Pages with the [React Grab](https://react-grab.com) your project already runs:

- Install `react-grab` in the project and import it in development, as its guide describes. Bud imports the same module, so it starts React Grab if your page hasn't yet.
- The component and line come from React's development build. Production builds have no source to read, and Bud never captures them.
- Picking in Pages uses React Grab's own overlay. Bud takes the element you choose and skips the copy.
- React Grab draws its toolbar over your site, so hide it from screenshots with `previews.css: '[data-react-grab] { display: none !important }'`.

## Your own picker

Add `picker()` with a browser module in your project, ending in `.ts`, `.tsx`, `.js` or `.jsx`. Its default export adds what your framework knows; every method is optional, and Bud's DOM picker covers whatever it leaves out.

```ts title="picker.ts"
import type { PickerAdapter } from '@bud.sh/app'

const picker: PickerAdapter = {
  // What the framework knows about an element.
  describe(element) {
    let block = element.closest('[data-component]')
    let component = block?.getAttribute('data-component')
    if (!component) return null
    let source = { component, file: null, line: null, column: null }
    return { label: component, source }
  },
  // The block that outlives the element's details.
  block(element) {
    return element.closest('[data-component]')
  },
}

export default picker
```

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'
import { picker } from '@bud.sh/app/plugins'

export default defineConfig({
  routes: ['/'],
  plugins: [picker('./picker.ts')],
})
```

| Method              | Returns                                                                                                          |
| ------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `describe(element)` | `{ label?, source? }`, or `null`. `source.file` is a path as your dev server serves it, such as `/src/card.tsx`. |
| `block(element)`    | The element's block, or `null` for Bud's landmarks.                                                              |
| `pick(signal)`      | The element the person chose with your own interface, or `null`. End it when `signal` aborts.                    |

Your dev server serves the module like any other, so it can import your framework's packages. It runs in your pages only while Bud is open, receives nothing from Bud, and what it returns is checked before Bud stores it. Keep it quick: Bud waits 15 seconds at most before it saves a comment without an element.


# Factory

Turn comments into tasks that coding agents finish in sandboxes, reviewed before anything is pushed.

The factory takes a comment from the canvas or the code to a reviewed branch. A person makes a task from the comment, starts it, and a coding agent works on it in a sandbox: a checkout of the project with its own dev server. When the agent is done, the task waits for review. Reviewers open the live preview, ask for changes or approve. Only approval pushes, and only the task's own `dev/` branch. The [Agents](#agents) screen lists every run, the ones waiting for you first, and starts new work from one message; each run's agent can be followed as a chat or in its terminal.

## The flow

| Step               | Who                       | What happens                                                                                                                                                                              | Task status    |
| ------------------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| **Make task**      | a person, on a discussion | A task with the discussion's text, a screenshot around a canvas comment with its point marked, the element there and its source line, and a link back                                     | Open           |
| **Start task**     | a person, on the task     | A sandbox with the project on a new `dev/<task>-<title>` branch, its dev server and the agent, which gets the task as its prompt                                                          | In progress    |
| Agent works        | the agent                 | Edits, runs the project's checks and commits; the task shows what it does                                                                                                                 | In progress    |
| Needs context      | the agent, then a person  | An agent that can't go on without something only a person knows asks instead of guessing; the task shows its question with **Needs context**, and your answer goes on in the same session | In progress    |
| Review             | a person                  | The agent's summary, **Open live preview**, **Request changes** or **Approve & push**                                                                                                     | Needs approval |
| **Approve & push** | a person, confirmed       | Bud pushes the branch, then pictures it on the canvas (**Show on canvas**)                                                                                                                | Ready to merge |
| Merge              | your Git host             | Bud notices once main contains the pushed commit and removes the sandbox                                                                                                                  | Merged         |

While a run waits for review, an answer or a retry, its sandbox sleeps after `sleepAfter` minutes, so it costs nothing; opening the preview, requesting changes or approving wakes it again, with its dev server and agent, in under a minute. **Discard** removes the sandbox at any point before the push; nothing reaches the remote. A failed step says why, with **Retry** when trying again may help. The agent asks with a last line `NEEDS CONTEXT: <question>`, which Bud's prompt tells it to use; the board and the task's discussions show **Needs context** until someone answers. The board shows each task's run as a badge, so you can see which tasks agents are working on and which wait for you. With the factory on, the board's columns are its steps, Open to Merged, with Closed folded at the end; other statuses, such as Beads' Pinned, Hooked and Deferred, wait behind **Other statuses**, and only show when tasks are in them.

## Turning it on

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'
import { local } from '@bud.sh/app/plugins'

export default defineConfig({
  routes: ['/'],
  // Where agents work; without a sandbox plugin, tasks can't start.
  plugins: [local()],
  factory: {
    setup: 'pnpm install',
    dev: { command: 'pnpm dev --port $PORT', port: 5173 },
    instructions: './bud/factory.md',
  },
})
```

Then open **Tasks**. The factory appears once everything it needs is there: a task store, a sandbox plugin whose sandboxes can run here (`boat()` needs its key) and the task store's statuses for its steps. Bud adds missing statuses to a store that allows it, such as Beads (see [Factory](/docs/app/getting-started/configuration#factory) in the configuration for the statuses and their options). Until then there are no run badges, **Start task** or Agent view; `bud doctor` says what's missing.

Without `dev`, runs have no preview: Agent view shows the agent's terminal and a shell, without a browser or the dev server's log.

| Option         | Default                     | Use                                                                                                                                                                                               |
| -------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setup`        | none                        | Runs once in each new checkout, such as installing dependencies.                                                                                                                                  |
| `dev`          | none                        | The dev server: `$PORT` in `command` is replaced by a free port, and `path` is where **Open live preview** lands. `env` adds values it needs; never secrets. Without it, there's no live preview. |
| `instructions` | none                        | A Markdown file in the project the agent reads with every task, such as how to run the checks.                                                                                                    |
| `branchPrefix` | `dev/`                      | The prefix of every run's branch.                                                                                                                                                                 |
| `sleepAfter`   | `5`                         | Minutes a sandbox stays up while its run waits for people; then it sleeps with its disk kept and wakes when needed. `0` keeps it up.                                                              |
| `push`         | `origin`, Git's credentials | `remote`, and `credential`: the server environment variable holding a token that may push, with the `username` your Git host expects (`x-access-token` for GitHub, `x-token-auth` for Bitbucket). |
| `statuses`     | Beads' names                | The task store's name for each step.                                                                                                                                                              |
| `herdr`        | `false`                     | Also runs the agent in [herdr](#live-status-with-herdr) in each sandbox, so Bud shows live whether it's working, needs input or is done.                                                          |

## Sandboxes

Agents work in the sandboxes of the project's sandbox plugin: `local()` or `boat()` from `@bud.sh/app/plugins`, or your own.

`local()` makes a Git worktree of your project in the system's temp folder for each run, on its branch, and runs the dev server and the agent there. Your own checkout, index and branch stay as they are; discarding or merging removes the worktree and the local branch. Runs that were working when Bud restarts fail and can be retried.

`local()` is for trying the factory on your own machine with tasks you trust. It isn't isolated: the agent runs as you, with your files, network and Git credentials. Bud keeps the agent from pushing to your remote, and withholds the push token, but that guards against mistakes, not against a task written to misuse it. For tasks from other people, use a sandbox provider such as boat.dev.

`boat()` runs each sandbox on [boat.dev](https://boat.dev): a full Ubuntu machine billed by the second. Set `BOAT_API_KEY` on Bud's server (`boat({ key })` names another variable; `type` picks the machine's size). Bud clones your project from its `push.remote` over HTTPS, with the push token when there is one, and starts sandboxes without any of your boat account's secrets: only the harness's `keys` and `dev.env` go in. Previews are boat's token-protected URLs; dev servers that check the host name need to allow `.on.boat.dev` (Vite: `server.allowedHosts`). An approved run's sandbox is stopped, a discarded or merged one deleted. Sandboxes stop on their own after two hours.

A harness of your own is a plugin with a `harness` (the `Harness` type from `@bud.sh/app/plugins`): how its server starts in the sandbox, a client for its sessions, prompts and events, and its model and keys.

Your own provider is a plugin, `definePlugin({ name, sandbox })`, whose `sandbox` makes a `SandboxAdapter` (both from `@bud.sh/app/plugins`) from what Bud passes: the checkout, the server's environment, the push remote as an HTTPS URL, clone credentials and the variables sandboxes must never see. The adapter creates a sandbox on a branch, runs commands, starts and stops named processes, exposes a port for the preview, and stops, resumes and destroys sandboxes. `@bud.sh/factory/testing` has the tests every provider passes.

## The agent

The agent is [OpenCode](https://opencode.ai) unless a harness plugin says otherwise. `opencode()` from `@bud.sh/app/plugins` picks the `model`, as OpenCode names it (`provider/model`), and `keys`, the server environment variables the model needs, such as `DEEPSEEK_API_KEY`, which Bud passes into sandboxes as secrets. The default, `opencode/deepseek-v4.1-flash-free`, is free and needs no key, enough for trying the factory:

```ts title="bud.config.ts"
import { defineConfig } from '@bud.sh/app'
import { boat, opencode } from '@bud.sh/app/plugins'

export default defineConfig({
  routes: ['/'],
  plugins: [
    boat(),
    opencode({ model: 'deepseek/deepseek-v4.1-flash', keys: ['DEEPSEEK_API_KEY'] }),
  ],
  factory: { setup: 'pnpm install', dev: { command: 'pnpm dev --port $PORT', port: 5173 } },
})
```

OpenCode starts as a server in the sandbox with a password Bud derives for each run. It can also read the project's whole task database, to find related tasks, duplicates and earlier decisions: Bud serves it read-only as an MCP server (`bud`, with `search_tasks`, `get_task`, `list_discussions` and `recent_changes`) behind a token for that run alone, which stops working once the run is pushed, merged or discarded. Agents reach it where they reach Bud: with `local()` on this machine, and from boat.dev when a hosted Bud sets its public address (see [Hosting](/docs/app/collaboration/hosting#the-factory)). Bud sends the task (with the comment's screenshot as an image, when it came from the canvas), your instructions and a few rules (commit on the branch, run the checks, never push, end with a summary) and follows its progress. Its model needs a key OpenCode can use in the sandbox, from `keys`, unless it's a free one; with `local()`, the agent also has your own OpenCode login.

## Agent view

A task with a run opens **Agent view** from its toolbar (or at `/tasks/<id>/agent`): the task's details beside a browser on the run's live preview and a terminal in its sandbox, or a chat with its agent. Opening it wakes a sleeping sandbox, and it keeps the sandbox up while it's open.

- **Browser.** The preview runs in a sandboxed frame on its own address, never Bud's, with back, forward, reload and the address. On boat.dev the preview gets a public address only while the sandbox is up, which goes down when it sleeps, is discarded or is merged. Pick an element with the crosshair and write what should change: Bud sends it to the agent as a change, with the page, the element and its source line, while the run waits for review. The preview goes through a small proxy Bud starts in the sandbox, which adds Bud's page bridge to every page and its DOM picker, so following and picking work in any project; with Bud's Vite plugin in the dev server, picking uses your configured picker, such as React Grab, and the plugin starts no Bud of its own there.
- **Terminal.** **Agent** opens the agent's own interface on the run's session (`opencode attach`), so you can talk to it and watch its tools run; **Dev server** follows the dev server's output; **Shell** opens a shell in the checkout. It's [xterm.js](https://xtermjs.org), drawn on the GPU, so complete programs work as they would on your machine: scrolling, the mouse and keyboard shortcuts such as OpenCode's Ctrl+P. Bud starts a small terminal bridge in every sandbox (Python 3's standard library is all it needs) and each tab talks to it over its own WebSocket through Bud's own address, so typing feels local even while the preview loads; a dropped connection picks up where it left off.
- **Chat.** A switch at the top right of the terminal's toolbar flips it to a chat with the agent, as in Codex: the prompts, the agent's replies and reasoning, its tool calls and the edits it made with their diffs, streaming while it works. Bud reads it from the agent's own session each time and keeps no copy, so what you type in the terminal's **Agent** tab shows here and the other way round. Write at the bottom: Enter sends and Shift+Enter starts a new line, and you can paste, drop or pick images. What you send goes where the run is: while the agent works it joins its turn, in review it asks for changes, and when the agent asked a question it's your answer. **Stop** ends the agent's turn, and the run waits for review with what it has. Runs that ended take no messages.
- **Layout.** With room, the terminal or chat sits beside the browser; its button hides it and the split button puts it below. In a narrow window or panel, a switch at the top picks the details or the agent's panes, and one in the toolbar the browser or the agent's pane. Bud remembers the layout and your choice of terminal or chat for you. The terminal and the agent's interface take your [theme](/docs/app/getting-started/configuration#theme), light or dark as Bud is, and switch with it while they run.

## Agents

**Agents** in the rail lists the factory's sessions, one per task with a run: those waiting for you first (a question, review or a failure), then those at work, then the ended ones, each with its state, when it last changed and who started it, live. Search finds one by its title or task. Opening one (`/agents/<task>`) shows its header, the task's title linking to its page, the branch and the run's actions (**Approve & push**, **Discard**, **Open live preview**, **Agent view**), and below it the agent's chat or terminal, with the same switch as Agent view.

Without a session open, the screen asks **What should the agent do?** in one large box. Sending it makes a task on the board from it (the first line as its title, the whole message as its description, you as its creator) and starts its run at once, then opens the session as it sets up; images pasted or dropped go to the agent with the task. On a phone, the list and a session are two steps, with a way back to the list. The screen shows once the factory is set up.

## Live status with herdr

With `herdr: true`, each sandbox also runs [herdr](https://herdr.dev), a terminal multiplexer that knows what coding agents do, and the agent's interface in it on the run's session, so Bud shows live what the agent is doing: **Working**, **Needs input** (such as a question or a permission it waits on), **Idle** or **Done**. The board's run badges, the task's **Agent** section and Agent view's toolbar show it within a second of each change, also for work you start from the agent's terminal or the chat while the run waits for review. The run's own steps stay as they are; herdr's status is the live activity on top.

Bud installs herdr's pinned release in each sandbox the first time, checked against its checksum (or uses herdr already there at that version), with herdr's OpenCode integration, so OpenCode reports its own state. If herdr can't be installed, the run goes on without the live status. Agent view's **Agent** tab keeps opening the agent's own interface on the same session in your theme, since herdr doesn't pass a program's color questions to the terminal; the chat, the terminal and herdr all follow one session. Without `herdr`, nothing of it runs and no live status shows.

## Security

- **Credentials stay on the server.** The push token reaches only Bud's push and remote check, through a credential helper set for that one command; it's never written to the checkout, its Git configuration or the agent's environment. Runs, task data and the browser never hold a credential.
- **Review comes first.** Nothing leaves the sandbox before a person approves, and Bud pushes only the run's branch, without force. Protect `main` on your Git host, and allow the push token only `dev/*`.
- **Tasks are prompts.** Anyone who can write a comment or task can tell an agent what to do. Agents may edit, run commands and fetch from the web inside their sandbox, so give sandboxes only what a task needs: a dev server that talks to production services can change production data.
- **Previews** are links to the run's dev server: with `local()`, a local port; on boat.dev, an unguessable public address while the sandbox is up, shown only to people signed in to Bud. They stop working when the sandbox sleeps, or the run is discarded or merged.
- **Terminals** are opened by Bud, with a password derived for each run that never leaves the server; a browser gets a ticket for one session that's good for a minute, which only opens a socket through Bud, from Bud's own pages; Bud checks it and pipes the socket on to the sandbox, so the browser never connects to the sandbox itself. Everyone who can act on runs can type into them, as into the agent itself.
- **The task database** is read-only for agents: the MCP tools can search and read tasks and discussions, never write, and each run's token stops working when the run ends. An agent can read everything in the task store, so keep secrets out of tasks and comments.
- **Who can act.** Everyone who can open Bud can start, approve and discard runs. A hosted Bud names them from its sign-in.


# Sharing

Tasks and discussions travel with your Beads workspace when you choose to sync.

Bud saves every task change, comment, reply and status to your storage adapter. With the default Beads adapter, they're written to the project's Beads database. Your working tree, staged changes and current branch are never touched.

## Sync

**Sync comments** in the canvas toolbar and **Sync** on the task board pull from your Beads remote and push your changes, the same as `bd dolt pull` and `bd dolt push`. They use the remote and credentials Beads already has, and never force-push.

1. You comment and sync.
2. A teammate syncs, replies and syncs again.
3. You sync to get the reply.

Pushing your source code doesn't sync Beads, and syncing Beads doesn't push code. Without a configured Beads remote, sync explains what's missing and your changes stay local.

## Conflicts

Every write checks that the thread or task hasn't changed since you loaded it. If it has, Bud keeps your draft and shows the current version, so you decide what to keep. Nothing is overwritten.

## Git comments

With the `gitComments()` plugin, discussions are committed to `refs/heads/bud/comments` through a temporary index instead. **Sync comments** then fetches that branch, merges it and pushes it to `origin`, or to the remote set with `comments: { remote: 'review' }`.

## What is stored

Messages, author names and emails, timestamps, statuses, anchors and task links. Everything you sync becomes part of the remote's history, so use a private remote for private projects.


# Working with agents

Hand a conversation to your own coding agent, or let Bud run one on a task.

There are two ways to get from a thread to a change. With the [factory](/docs/app/guides/factory) set up, make a task from the thread and start it: a coding agent works on it in a sandbox, you follow it on the **Agents** screen, and Bud pushes its branch only after someone approves. Bud never starts a run on its own.

Or keep your own agent, in your editor or terminal. Bud doesn't send your comments to it; you bring the context yourself, and a good thread makes that a copy and paste.

## A useful handoff

Include the page, what should change and how to check it:

```text
Route: /collection
Change: The title pushes the first row below the fold. Keep the title on one line
        and the first row visible at 1440px.
Check: / and /collection in both themes.
```

For code comments, include the file and lines. If a comment is outdated, give the original lines and ask the agent to find the current code.

## Close the loop

Set the thread to **In progress** while the work happens. When the page updates, check it on the canvas, reply with what changed and move the thread to **Needs approval** or **Resolved**.

## Docs for agents

Every docs page is available as Markdown: add `.md` to its URL or send `Accept: text/markdown`. [`/llms.txt`](/llms.txt) lists every page and [`/llms-full.txt`](/llms-full.txt) contains all of them.


# Hosting on Cloudflare

Share the canvas, pages, tasks and agents with your whole team on Cloudflare, always available, with every branch captured in its own container.

A hosted Bud shares your canvas, pages and tasks with everyone you give a password: every route of main and your activated branches at every viewport, with their comments, differences and tasks, and with [the factory](#the-factory) its agents, which work on boat.dev. It's for a GitHub repository, opens instantly at any time, and nothing runs while nobody looks.

The bud.sh team's own runs at [app.bud.sh](https://app.bud.sh): Bud for this website, from the hosting file in its repository (`apps/web-marketing/bud.hosting.json`), with a production build for captures and the factory on boat.dev.

## How it runs

- **Bud** is served by a Worker: its interface as static files, and its server in a Durable Object that reads pictures and maps from R2 and tasks and comments from D1. It's the same Bud you run locally, with the canvas, Pages, Tasks and, with the factory, Agents. Pages shows main's captured pictures instead of your running site, at the viewport you pick, without browser controls; Files needs a checkout, so a hosted Bud leaves it out.
- **Branches** are captured in a container of their own per commit. It checks out the commit, installs dependencies, starts your dev server in development mode, captures every route in `bud.config.ts` at every viewport with its element map, and is destroyed. One capture runs at a time; one that takes longer than 45 minutes is stopped, and one whose container disappears is captured again once.
- **The canvas** opens on main. Comments made on a branch stay with that branch, and on the viewport they were made on. They're signed by the hosted user.
- **Changes** are found every five minutes by reading the heads of main and your activated branches from GitHub. No webhook is installed. A branch that moved is captured on its own at most once every 4 hours, at its newest head; a branch's first capture starts at once. **Capture now** beside a branch in the sidebar captures its head right away, also when its pages changed without a commit, as content from an API does.

## What you need

- A Cloudflare account on Workers Paid, with an API token that can edit Workers, Durable Objects, Containers, R2, D1 and Queues.
- A GitHub repository, and a fine-grained token for it (and its submodules) with **Contents** read and write: Bud reads branches and checks out commits with it, and the factory pushes its `dev/` branches with it.
- Docker, which builds the capture image.

Put the credentials in the workspace's `.env`, which is never committed:

```sh
CF_API_TOKEN=…
GITHUB_TOKEN=…
```

## Deploy

Describe the project in a hosting file, kept out of the repository:

```json
{
  "project": "/path/to/your/checkout",
  "repository": "owner/repository",
  "submodules": { "ds": "owner/design-system" },
  "main": "main",
  "files": ["bud.config.ts"],
  "captureIntervalHours": 4
}
```

`project` is your checkout's folder with `bud.config.ts`, relative to the hosting file. `path` is that folder inside the repository when it isn't the root, such as a site in a monorepo (`"path": "apps/site"`); captures install the whole repository with the lockfile at its root (Bun, pnpm or npm) and run Bud in that folder. `domain` serves Bud on a host name of your own on the same Cloudflare account, such as `"domain": "app.example.com"`; without it, Bud is on your workers.dev subdomain. `captureIntervalHours` sets how long a branch's automatic captures wait after its last one (4 by default).

Captures build each branch with your `branches.build` recipe, or your Vite dev server without one. A dev server compiles every page as it's asked for, so a capture loads only two pages at a time and takes a while. For a faster capture, give the hosting file its own recipe and how many pages to load at once:

```json
{
  "branchBuild": {
    "build": "npm run build",
    "serve": "npm run preview -- --port $PORT"
  },
  "captureConcurrency": 6
}
```

Captures walk your [flows](/docs/app/getting-started/configuration#flows) too, with each branch's own scripts. A flow script your repository doesn't hold yet can come from your checkout through `files`, such as `"files": ["bud.config.ts", "bud/flows/booking.ts"]`.

`branchBuild` replaces `branches.build` for hosted captures only, so local Bud keeps your dev server. Its commands run in the branch's checkout and find the captured paths in `BUD_ROUTES`, one per line, for builds that render only those pages; `serve` listens on `127.0.0.1:$PORT`. A production build serves pages quickly enough for six or more at once. Keep source maps reachable next to the built files, so comments still name their component and source line.

Captures run one at a time, five minutes apart: one started straight after another ran slow or failed. A capture that fails, such as a build that stalled, is tried once more at the next check; if that fails too, the branch waits for its next commit. A deploy waits for a capture under way to finish first, since it replaces the capture container; `--force` deploys at once.

Then deploy from a checkout of Bud's repository, where the hosting scripts live (they aren't part of the npm package):

```sh
node packages/app-cloudflare/scripts/deploy.ts --hosting .hosted/project.json
```

The script builds Bud, reads your `bud.config.ts` (routes, viewports, logo) into the Worker, creates the `bud-previews` bucket, the `bud-work` database and the `bud-captures` queue when they're missing, generates a user and password into `.env` (`BUD_HOSTED_USER`, `BUD_HOSTED_PASSWORD`), stores every credential as a Worker secret and deploys the `bud-hosted` Worker. It then signs in as that user and fails unless the page loads this build's interface and assets. It only creates or changes resources whose names start with `bud-`. Deploy again after changing `bud.config.ts`.

### The factory

A hosted Bud runs the [factory](/docs/app/guides/factory) on boat.dev, whatever sandbox plugin the project uses for development. Give the hosting file the factory as the host runs it, which replaces the project's own:

```json
{
  "factory": {
    "setup": "npm install",
    "dev": { "command": "npm run dev -- --port $PORT --host 127.0.0.1", "port": 5173, "path": "/" }
  }
}
```

and add to `.env`:

```sh
BOAT_API_KEY=…
```

Agents read the task database from Bud's public address when you add it to the Worker's settings as `BUD_PUBLIC_URL`, such as your workers.dev URL; without it they work without. Sandboxes clone the repository and push their run's `dev/` branch with `GITHUB_TOKEN`, which each of those commands gets alone; it never stays in the sandbox, where the agent works. Bud pushes nothing but run branches; to stop the token itself from touching `main`, protect `main` with a ruleset and use a token of an account the ruleset doesn't let through. Bud's server carries runs on with its own timer while visitors are away, and checks pushed branches for a merge through GitHub. A dev server that checks host names must allow `.on.boat.dev`; for Vite, `__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS` in `dev.env` does without changing your config. Statuses: open **Tasks** and choose **Add factory statuses**.

### On your own server

Your company platform can serve the same Bud behind its own sign-in, on the Worker's pictures, tasks, comments and branches. Install `@bud.sh/app` there and hand it every request once your platform has signed the person in; they author the request's comments and task edits:

```ts
import { createNodeHost } from '@bud.sh/app/node-host'

const bud = await createNodeHost({ url: process.env.BUD_URL, token: process.env.BUD_REMOTE_TOKEN })
// In your server, after sign-in, for every path of Bud's site:
return bud.fetch(request, { author: { name: user.name, email: user.email } })
```

Bud answers every path of its site, its files included, so give it a host name of its own. Factory runs are carried on by the Worker: your server passes its factory requests on, with the signed-in person, and needs no keys of its own. `BUD_URL` is the Worker's address; the Worker opens its remote API, `/__bud/remote/*`, only to `BUD_REMOTE_TOKEN`: add a long random value to `.env` and deploy again, since a Worker without one has no remote API. Keep `@bud.sh/app` at the version the Worker was deployed with.

To try a change first, `node packages/app-cloudflare/scripts/dev.ts --hosting .hosted/project.json` runs the same Worker on your machine, with local storage or, with `--remote`, your account's.

To work on Bud itself against what your team sees, add `--hmr`: Bud's own development server then serves the hosted app with hot reloading, on your account's pictures, tasks and comments with `--remote`. Tasks and comments you write there reach the hosted Bud. Pictures and maps are kept on your machine after their first download, since they never change. With Portless running it's at `https://hosted.bud.localhost`, otherwise at `http://127.0.0.1:5175`.

```sh
node packages/app-cloudflare/scripts/dev.ts --hosting .hosted/project.json --remote --hmr
```

## Time and cost

Measured with a project of 131 routes at two viewports (1440 and 390 pixels) on `standard-4` capture containers (4 vCPU, 12 GiB):

| Step                                                        | Time        |
| ----------------------------------------------------------- | ----------- |
| Opening Bud after a while without visitors                  | 1–2 s       |
| Opening Bud while someone uses it                           | about 0.1 s |
| A branch head: checkout, submodules and cached dependencies | about 20 s  |
| All 131 routes captured at both viewports with their maps   | 13–18 min   |

A branch capture costs memory and disk for the minutes it runs, and CPU only while it works. Captured from a dev server, two pages at a time, 131 pages at two viewports took 20 to 40 minutes and about $0.10–$0.20; from a production build with `branchBuild` and six pages at a time, the same capture takes about 10 minutes, build included, and about $0.07. One viewport takes about half as long and costs about half as much. With one automatic capture per branch every 4 hours, main and one other branch that both keep moving cost at most about $25 a month with a production build (about $45 from a dev server), plus any captures started by hand. Serving Bud costs almost nothing: Worker requests and the server object's time while pages are open, a few dollars a month even if a tab stays open all day. Pictures, tasks and comments stay within the storage Workers Paid includes.

Captures use Chromium inside the container, the version your local Bud uses, so hosted and local pictures match. Browser Run was measured too: every page's modules crossing from the browser to the container made each page take about 25 seconds instead of 5, and its older Chrome renders differently.

## Limits

| Limit                                 | Value           |
| ------------------------------------- | --------------- |
| Capture containers at a time          | 1               |
| Longest capture                       | 45 minutes      |
| Pictures of a deactivated branch kept | 30 days         |
| Branch check                          | every 5 minutes |
| Automatic captures per branch         | every 4 hours   |


# Troubleshooting

Fix connection, comment and sync problems.

## Bud doesn't start

Read the terminal error. Keep exactly one `bud.config.ts` or `bud.config.js` in the Vite root, with a default export and at least one route. If you set `port`, it must be free and different from your app's port.

## A screen or button is missing

Bud shows a feature only once it's set up: no **Tasks** without a task store, no comments without a discussion store, no **Agents** or **Start task** without the factory. Run `npx bud doctor` in your project; it says what each missing piece needs.

## A page is blank

The artboard names the problem. If Bud can't start a browser, run `npx playwright install chromium` or install Google Chrome. If the page fails to load, open it directly: Bud's headless browser has no cookies of its own, so pages behind authentication or with an untrusted certificate don't render. Bud doesn't bypass your app's policies.

## Edits don't show up

If the page updates when opened directly but not on the canvas, your server probably skips `transformIndexHtml`. See [Vite and custom servers](/docs/app/getting-started/vite). After a config change, use the URL printed by the restarted server.

## Nothing updates live

The icon at the bottom of the navigation rail shows the live connection: connected, reconnecting or offline. Bud reconnects by itself, and after a restart it reloads everything open. Your browser keeps a copy of what it last saw, per project; clearing the site's data only removes that copy, and Bud reads it again.

## I can't comment

- Comments need a ready storage adapter. With the default Beads adapter, install `bd` 1.3.1 or newer and run `bd init` in the project.
- Set `user.name` and `user.email` with `git config`.

## A save failed

Your text stays in the composer. If someone else changed the thread, Bud reloads it and lets you send again deliberately. After a restart, open the newly printed URL; the old session is no longer valid.

## Comments aren't shared

Comments save locally. Use **Sync comments**, and make sure your Beads remote exists and accepts pushes (`bd dolt push`). Your teammate has to sync too.

## I can't find a thread

Clear the search and enable every status filter, including **Resolved**.


# Release notes

What changed in each release of @bud.sh/app.

## 0.7.0

The release that lets agents do the work: a comment becomes a task, an agent finishes it in a sandbox, and you review it before anything is pushed.

### New

- **The factory.** Make a task from a comment on the canvas or in the code and start it: a coding agent ([OpenCode](https://opencode.ai)) works on it in a sandbox, a worktree on your machine or a fresh one on [boat.dev](https://boat.dev), on its own `dev/` branch with its own dev server. When it's done the task waits for review: open the live preview, ask for changes, or approve, which pushes only that branch. See [Factory](/docs/app/guides/factory).
- **Agent view.** A task's live preview beside the agent's terminal, with tabs for the agent, the dev server's log and a shell, and a switch to **Chat**: the agent's whole session (prompts, replies, tool calls and edits with their diffs) as it happens, where you can send messages, with images, or stop the agent.
- **Agents.** A screen of every agent session, the ones waiting for someone first, with the open session's chat or terminal beside the list. One message in its box makes a task and starts its run.
- **Live agent status.** With `factory.herdr`, Bud runs the agent in [herdr](https://herdr.dev) as well and shows whether it's working, needs input or is done.
- **Hosting on GitHub.** A hosted Bud on Cloudflare now reviews a GitHub repository, also a project in a folder of a monorepo (`path`), on a host name of your own (`domain`); captures install with the repository's lockfile (Bun, pnpm or npm), and the factory runs on boat.dev. See [Hosting on Cloudflare](/docs/app/collaboration/hosting).
- **Themes from three colors.** A theme's `background`, `foreground` and `primary` are enough: Bud derives every other token, the terminal and the agent's interface included. A theme that also sets its `shell` gets its tints from it.

### Changed

- **A simpler tasks screen.** The board is agent-driven: no priorities and no blocked-by dependencies, and cards move by hand only to Closed and back. Each task shows the person who made it, and the board filters by person. Epics and linked tasks stay.
- **Only what's set up shows.** A screen, button or notice appears only once its feature is set up; `bud doctor` explains the rest.
- **Fewer dependencies.** The docs' Markdown pipeline, the slugger, YAML, the array diff, the picture comparison, the safe regular expressions and the icons are Bud's own code now.
- **Node 26.12** or newer is required.

### Breaking for hosted Bud

- GitHub replaces Bitbucket: `GITHUB_TOKEN` reads the repository and pushes the factory's `dev/` branches, and `BITBUCKET_API_KEY`, `BITBUCKET_API_EMAIL` and `BUD_FACTORY_PUSH_TOKEN` are gone.
- The deploy no longer makes a `BUD_REMOTE_TOKEN`; add one to `.env` when another host needs the remote API.
