# The Web Control Panel

`forge docs serve` runs a **browser control panel** for the current repo. Unlike
the static site `forge docs build` produces, this one is **interactive**: it
reads `.forge/` live and can act on it — run verify, archive a change, clear a
tripped gate, waive a finding.

```sh
forge docs serve                        # http://127.0.0.1:4321
forge docs serve --addr localhost:4000  # pick a port
forge docs serve --cwd ../other-repo    # point at another repo
```

::: info Shipped inside the binary
The panel is a Svelte app compiled to a static bundle and embedded in the
`forge` binary with `go:embed`. There is **no Node runtime at serve time and no
network access** — the binary serves the whole app itself.
:::

## Bound to loopback by default

`serve` binds **`127.0.0.1:4321`**, so the panel is not reachable from other
machines unless you deliberately override `--addr`.

::: warning `--addr` can expose a mutating surface
This panel **changes state**. Binding it to a non-loopback address puts those
actions on your network. Keep the default unless you have a specific reason,
and never expose it on an untrusted network.
:::

## What it can do

Reads are plain `GET`s. Everything that changes state is a guarded `POST`.

| Action | What it does |
| --- | --- |
| Run verify | Runs `verify` for a change — the same gate as the CLI, breaker included |
| Archive | Promotes a finished change (still blocked unless every task is `done`) |
| Reset breaker | Clears a tripped [verify circuit-breaker](/harness/circuit-breaker) |
| Reset budget | Clears the change's [spend ledger](/harness/budget) |
| Waive / reset secscan | Accepts a reviewed [secscan](/harness/guardrails) finding, or clears findings |
| Promote learning | Promotes continuous-learning candidates into memory |
| Change create / rename / delete / save | Manage change folders and edit their artifacts |

::: warning Clearing a gate is a human decision, not a bypass
Resetting the breaker or budget, and waiving a secscan finding, are exactly the
**human-gated** steps the [enforcement doctrine](/harness/overview) expects —
a person deciding to continue *after* looking. The gates still evaluate the same
deterministic evidence; the panel just gives you a button instead of a flag. It
never lets the agent clear its own gate.
:::

## How mutations are protected

Every `POST /api/actions/*` route passes through the same guard, and **all three
checks must hold**:

```mermaid
flowchart LR
  R[POST /api/actions/*] --> M{POST?}
  M -- no --> X405[405]
  M -- yes --> O{Same origin?}
  O -- no --> X403[403]
  O -- yes --> T{X-Forge-Token matches?}
  T -- no --> X403
  T -- yes --> A[Action runs]
```

1. **POST only** — a `GET` can never trigger an action.
2. **Same origin** — the `Origin` (or `Referer`) host must match the server host.
   A mutation carrying **neither** header is rejected, not trusted.
3. **Session token** — a 16-byte token minted when the server starts. It is
   **runtime-only and never written to disk**; restarting `serve` invalidates it.

The token is injected into the served page as
`<meta name="forge-token" content="…">`, and the app sends it back as the
`X-Forge-Token` header. A `POST` without it fails closed:

```text
$ curl -X POST http://127.0.0.1:4321/api/actions/verify
invalid token          # HTTP 403
```

## Where it fits

| Surface | Command | Nature |
| --- | --- | --- |
| **Web control panel** | `forge docs serve` | Interactive — reads and acts |
| Static spec-viewer | `forge docs build` | Offline HTML, read-only, publishable |
| Terminal panel | `forge panel` | TUI, **observe-only** |

All three render the same `.forge/` state, so they never disagree — they differ
only in how much they let you do. See the
[Spec-Viewer Dashboard](/features/dashboard) for the static build.

## Working on the panel

The Svelte source lives in `web/`; the compiled bundle is committed under
`internal/webui/dist/` because the binary embeds it.

```sh
make web           # build the bundle into internal/webui/dist
make web-check     # type-check (svelte-check)
make web-test      # unit tests (vitest)
make web-verify    # fail if the committed bundle is stale vs web/src
```

::: tip Rebuild the bundle when you touch `web/src`
Because the bundle is committed and embedded, editing `web/src/` without
rebuilding would ship an outdated UI. `make web-verify` catches exactly that,
and it runs as part of `make verify-all`.
:::

## Next steps

- [Spec-Viewer Dashboard](/features/dashboard) — the static, publishable build.
- [The forge CLI](/reference/cli) — the full `forge docs` synopsis.
- [Enforcement doctrine](/harness/overview) — why clearing a gate is a human act.
- [The session timeline](/harness/timeline) — the events the panel renders.
- [Verify circuit-breaker](/harness/circuit-breaker) — the gate the panel can reset.
