vibecheck0.3.0
Concepts

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.

The beacon round-trip through VibeStoreOn the write path, the browser widget's beacon POSTs each snapshot to the MCP HTTP receiver on port 4200, which validates it and folds it into the immutable VibeStore. On the read path, the AI agent calls the MCP tools, which read the same store and return issues and fix suggestions.WRITE PATHREAD PATHBROWSERwidget beaconBeaconClientMCP HTTP:4200 receiverzod-validatedPOST/api/snapshotIMMUTABLEVibeStorelatest + 100 issuesAI AGENTMCP toolsget_detected_issuesreadsissues + fixes
One store, two sides. The browser writes snapshots in over HTTP; the agent reads issues out through the MCP tools. VibeStore is the shared middle.

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.