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):
<VibeCheck beaconUrl="http://127.0.0.1:4200" projectId="my-storefront" />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):
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.