vibecheck0.3.0
Concepts

Collectors

The six browser measurements VibeCheck records, and the Collector interface.

Collectors are the measurement layer. Each one watches a single browser signal — frame rate, long frames, memory, web vitals, resources, or console volume — and exposes its current reading as a plain stats object. The engine reads all six every snapshot tick; the detectors analyze what they produce.

How a snapshot becomes issuesThe six collectors — fps, long frames, memory, web vitals, resources, console — fan in to one immutable snapshot taken every 500 milliseconds. The snapshot fans out to the detectors, which analyze it and emit issues as a VibeIssue array.COLLECTORSDETECTORSfpsloafmemoryweb vitalsresourcesconsoleEVERY 500MSSnapshotone immutable objectdom-bloatmemory-leakseo audit+ 10 moreOUTPUTissuesVibeIssue[]
Six collectors fan in to one immutable snapshot every 500ms; the snapshot fans out to the detectors, which emit the issues your agent reads.

The Collector<T> interface

Every collector implements the same four-method contract, so the engine treats them uniformly and you can build your own:

interface Collector<T> {
  start(): void
  stop(): void
  getStats(): T
  onUpdate(callback: (stats: T) => void): () => void
}

onUpdate returns an unsubscribe function. getStats is a pure read of the current value — safe to call at any cadence.

The six collectors

CollectorMeasuresBacked byStats shape
FrameRateCollectorFPS, avg/max frame time, dropped frames, smoothness.requestAnimationFrame over a 1s window.FrameRateStats
LongFrameCollectorLong Animation Frames with per-script attribution.PerformanceObserver (long-animation-frame).LongFrameStats
MemoryCollectorJS heap used/total (MB) and used %.performance.memory, polled every 2s.HeapMemory | null
WebVitalsCollectorLCP, INP, CLS with good / needs-improvement / poor ratings.PerformanceObserver.WebVitalsStats
ResourceCollectorTransfer bytes by type (JS/CSS/image/font) + the largest resources.PerformanceResourceTiming, polled every 5s.ResourceStats
ConsoleCollectorCounts of console.log / warn / error.Wraps the three methods, counts, and calls through.ConsoleStats

Availability varies by browser

LongFrameCollector needs the LoAF API and MemoryCollector needs performance.memory — both Chromium-only. Where an API is missing the collector stays inert and its stats read empty (or null for memory), so the host is never broken. See feature detection.

Two ways to consume a collector

Drive one directly:

import { FrameRateCollector } from '@wcgw/vibe-check-core'

const fps = new FrameRateCollector()
fps.start()

const off = fps.onUpdate((stats) => {
  console.info(stats.fps, stats.droppedFrames, stats.smoothness)
})

// later
off()
fps.stop()

Or, in React, use the matching hook — it manages the collector lifecycle for you:

import { useFrameRate } from '@wcgw/vibe-check'

function Meter() {
  const { fps, smoothness } = useFrameRate(true) // pass enabled
  return <span>{fps} fps · {smoothness}% smooth</span>
}

Each collector's reading also appears as a field on the VibeSnapshot the engine assembles — frameRate, longFrames, memory, webVitals, resources, and console.

Notes on specific collectors

  • Console — the wrapper only counts; it forwards every call to the original method, so your logs still print. On stop() it restores the console only if its own wrapper is still installed, so it never clobbers another patcher.
  • Web vitals — CLS is computed with the standard session-window algorithm (1s gap / 5s max window) and shifts with recent user input are excluded.
  • Frame rate — samples are kept in a fixed-capacity RingBuffer, so the hot rAF path allocates nothing per frame.