# The Beacon

> How snapshots leave the browser and reach the MCP server.

The beacon is the one network hop in VibeCheck. When the engine has a
`beaconUrl`, its `BeaconClient` serializes the current `VibeSnapshot` to JSON and
wraps it with `projectId`, browser instance ID, and page URL, then POSTs the
envelope to `<beaconUrl>/api/snapshot` on an interval.

## Turning it on

Pass `beaconUrl` to the widget (or to the engine directly):

```tsx
<VibeCheck beaconUrl="http://127.0.0.1:4200" projectId="my-storefront" />
```

```ts
const engine = new VibeCheckEngine({
  beaconUrl: 'http://127.0.0.1:4200',
  projectId: 'my-storefront',
})
```

Without a `beaconUrl` the engine runs fully local — collectors and detectors
work, the overlay updates, but nothing is sent anywhere.

## Delivery, honestly

`BeaconClient` delivers with `fetch(..., { keepalive: true })` rather than
`navigator.sendBeacon`, on purpose: `sendBeacon` returns `true` the moment the
browser *queues* the payload — even when nothing is listening on the port — which
made the connection indicator lie. `fetch` lets the client read the real
`response.ok`, so status reflects an actual server acknowledgement. It falls back
to `sendBeacon` only when `fetch` is unavailable. Delivery failures are recorded
and surfaced but never thrown — monitoring must never break the host app.

## `BeaconStatus`

The engine exposes the live delivery state via `getBeaconStatus()` (the widget's
Settings panel renders it):

```ts
interface BeaconStatus {
  readonly configured: boolean
  readonly projectId: string
  readonly instanceId: string
  readonly lastAttemptAt: number | null
  readonly lastOk: boolean | null
  readonly projectStatus: ProjectStatus | null
  readonly statusError: 'hub-offline' | null
}
```

`projectStatus.state` is `no-agent`, `watching`, `busy`, or `stale`. The widget
maps those states to waiting, connected, working, and disconnected instructions,
and also surfaces a recently rejected second watcher.

## On the receiving end

The hub accepts the envelope at `/api/snapshot`, validates it, and folds it into
that project's immutable store. The widget polls the project status route and
dispatches selected issues to its queue. Browser routes allow CORS; bridge-only
`/internal` routes reject browser-originated requests.

**Cadence**

  The beacon POSTs every `beaconIntervalMs` (default **2s**), and fires once
  immediately on `start()`. This is separate from the engine's 500ms
  `onSnapshot` UI cadence.
