Architecture
This page explains how peekβs major systems work and how they fit together.
System Overview
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β peek CLI β
β snap | check | dev | scene | pool | clean β
ββββββββββββββββ¬ββββββββββββββββ¬ββββββββββββββββββββββββ€
β β β β
β snap.ts β runner.ts β scene.ts β
β Web/native β Check β Game engine β
β screenshotsβ orchestratorβ spatial checks β
β β β β
ββββββββ¬ββββββββΌββββββββββββββββ€ β
β β β β β
β pool.ts β checks/ β β
β Browser β 6 layout β β
β pooling β checks β β
β β β β
ββββββββ΄ββββββββΌββββββββββββββββ€ β
β β β β
β browser.ts β reporter.ts β β
β Chrome β text/json/ β β
β detection β junit output β β
β β β β
ββββββββββββββββ΄ββββββββββββββββ΄ββββββββββββββββββββββββ€
β β
β native.ts frameworks/ β
β macOS ScreenCaptureKit Auto-detect & dev server β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββBrowser Pooling
The pool is peekβs most important optimization. Instead of launching Chrome on every invocation (~3.8s), peek maintains a persistent Chrome instance that survives between CLI calls.
How It Works
- First call:
acquireBrowser()finds no pool entry. It launches Chrome as a detached process with--remote-debugging-port=0(Chrome picks a free port). - Pool file: The WebSocket endpoint, PID, and timestamps are written to
~/.peek/pool.json. - Subsequent calls:
acquireBrowser()reads the pool file, verifies the process is alive, and connects viapuppeteer.connect(). Each call gets its ownPageinstance. - Disconnect vs close: After each snap/check, peek calls
browser.disconnect()(notclose()). Chrome stays alive. - Idle timeout: If the pool has been idle for 5 minutes, the next invocation kills it and launches a fresh browser.
- Concurrency safety: File-based advisory locking (
~/.peek/pool.lock) prevents race conditions when multiple agents access the pool simultaneously.
Opting Out
Use --no-pool to force a fresh browser that is closed after the command:
peek snap http://localhost:3000 --no-poolThis is useful in isolated CI environments or when debugging browser state issues.
Chrome Detection
browser.ts resolves the Chrome executable path using a 3-tier strategy:
- System Chrome β Uses
chrome-launcherto find installed Chrome/Chromium. - Cached Chromium β Checks
~/.cache/peek/.chromium-pathfor a previously downloaded binary. - Auto-download β Downloads Chromium via
@puppeteer/browsersand caches it in~/.cache/peek/.
This means peek works on any machine without manual browser setup.
Screenshot Pipeline
When you run peek snap <url>:
- Browser acquisition β Pool or fresh launch (see above).
- Setup script (optional) β If
--setupis provided, a temporary page runs the script to establish session cookies, then closes. - Page creation β A new
Pageis opened with the specified viewport (default: 1280x720). - Navigation β
page.goto()withwaitUntil: networkidle2(configurable). - Wait (optional) β Additional wait via
--wait <ms>. - Screenshot β
page.screenshot()to the output path (or an auto-generated path in/tmp/peek/). - Checks (optional) β If
--checksis set, layout checks run on the same page before it closes. - Output β The absolute file path is printed to stdout.
Check Pipeline
When you run peek check <urls...>:
- Browser launch β Always uses
launchBrowser()(not the pool), since checks may run on multiple URLs with different viewports. - Setup script (optional) β Runs first if
--setupis provided. - Per-URL, per-viewport loop β For each URL and viewport combination:
- New page, set viewport.
- Navigate with
waitUntil: networkidle0. - Optionally wait for SPA hydration (
--wait-for-hydration). - 500ms settle delay.
- Run all checks (or filtered subset via
--only).
- Output β Formatted results per URL/viewport, then summary.
- Exit code β Determined by
--fail-onthreshold.
Check Architecture
Each check implements the Check interface:
interface Check {
name: string;
description: string;
run: (ctx: CheckContext) => Promise<CheckResult>;
}The CheckContext provides the Puppeteer Page and the merged CheckConfig. Each check uses page.evaluate() to run DOM inspection logic in the browser context, then returns issues with element selectors, severity, descriptive messages, and fix hints.
All 6 checks respect:
- The global
ignoreselector list from config - Per-check
ignoreselectors - The
data-peek-ignoreHTML attribute
Native Capture Architecture
Native capture (macOS only) bypasses the browser entirely:
- Swift helper β On first use, peek compiles a Swift helper (
peek-capture) from an embedded source string. The binary is cached at~/.cache/peek/peek-capture. - Window enumeration β The helper calls
CGWindowListCopyWindowInfoto list on-screen windows with their IDs, owners, and PIDs. - Window matching β peek finds the target window by app name (
--app) or PID (--pid). - Capture β
SCScreenshotManager.captureImage()captures the window at 2x resolution via ScreenCaptureKit. - Output β The PNG is written to disk and the path is printed to stdout.
Requires screen recording permission (System Settings > Privacy & Security > Screen Recording).
Scene Check Architecture
Scene checks (peek scene) work without a browser:
- Read manifest β The JSON file describes a room with entities (positions, sizes, names).
- Run spatial checks β Four checks: entity overlap, out-of-bounds, zero-size, and off-camera visibility.
- Output β Same
HealthResultsformat as browser checks, formatted via the same reporter.
This is designed for game engine UIs (Bevy, Unity) where the rendering surface is a canvas opaque to DOM inspection.
Framework Detection
The peek dev command auto-detects the project framework by inspecting package.json dependencies:
| Framework | Detection key | Default port |
|---|---|---|
| Next.js | next | 3000 |
| SvelteKit | @sveltejs/kit | 5173 |
| Nuxt | nuxt or nuxt3 | 3000 |
| Remix | @remix-run/react | 3000 |
| Astro | astro | 4321 |
| Create React App | react-scripts | 3000 |
| Vite | vite | 5173 |
Hydration detection is automatic: for frameworks that render server-side HTML and then hydrate on the client (Next.js, SvelteKit, Nuxt, Remix, Astro, CRA), peek waits for the hydration to complete before running checks.
Programmatic API
peek exports its core functions for use as a library:
import { snap, checkPage, createChecker, runSceneChecks } from 'peek';
// Screenshot
const result = await snap({ url: 'http://localhost:3000' });
console.log(result.path);
// Check a page (pass a Puppeteer Page)
const results = await checkPage(page, { ignore: ['.skip'] });
// Reusable checker with config
const checker = createChecker({ ignore: ['.ads'] });
const results = await checker.run(page);