ttushar.

Sole developer · 2026

Uptime Monitor

Self-hosted monitoring that opens every client site in real Chromium instead of pinging it, because a server returning 200 OK and a website that actually works are different claims. Eighteen detectors, durable scheduling, screenshot evidence on every alert.

TypeScriptPlaywrightPostgreSQLInngestReact
Role
Sole developer
Engagement
One week, July 2026 — 85 commits
Method
Written spec, nineteen test-first tasks
Status
Self-hosted, watching client sites

The problem

What was broken

An agency runs a lot of client WordPress sites. The monitoring tool pings each one, gets 200 OK, and stays quiet — while a visitor opening the same URL in Chrome sees something else entirely. The tool is technically correct and completely useless, and the way you find out is a client email asking why their site has been down since Tuesday.

Up is not the same as working

A site flagged for malware shows visitors "Deceptive site ahead." A JavaScript bundle that throws on load leaves a white screen. WordPress maintenance mode gets left on for three days. In every case the server returns 200 and the ping-based monitor sees nothing.

Evidence, or an argument

An alert saying a site is broken is a claim the client can dispute. Without a screenshot of what visitors actually saw, the conversation is an argument instead of a fix.

Alert fatigue kills the tool

If a broken site pages you every five minutes for three hours, people mute the channel — and a muted monitoring tool has negative value.

The monitor as the outage

Opening headless browsers against client sites is invasive. Six simultaneous Chromium sessions against one client's shared host is a good way to cause the outage you were watching for.

The product

What I built

Uptime Monitor is the browser-truth layer of a three-layer monitoring stack. Kuma and Gatus keep answering the cheap questions unchanged; my service — about 3,600 lines of TypeScript across 72 files, with 38 test files spanning unit, integration and end-to-end — opens each site in real Chromium, gathers a single evidence object per run, and passes it through eighteen pure-function detectors. Findings feed an incident state machine tuned against alert fatigue, alerts go out through Discord and SendGrid with the failing screenshot attached, and a separate React dashboard of about 2,200 lines merges network uptime with browser health.

The architecture is interfaces wired by hand in one composition root — detection, alerting, screenshot comparison and persistence are each swappable implementations, no DI framework, no globals. Six Postgres tables, umzug migrations at boot, and screenshots stored per run, pruned, and diffed to drive the visual detector. I built it solo in about a week in July 2026, 85 commits, working from a written spec broken into nineteen test-first tasks before any implementation code existed — which is why a week-old project has a coherent architecture instead of the shape of whatever I thought of first.

Capabilities

Key features

Real-browser checks

Every site opens in actual Chromium on its schedule, and the system looks at what rendered — not whether a port answered.

Eighteen detectors

Security interstitials, DNS and SSL failures, blank pages, SPA crashes, defacement, off-domain redirects, maintenance mode, missing selectors, keyword rules, console error storms, slow renders, visual diffs and more.

An incident lifecycle, not a firehose

A finding must recur before it alerts, transient noise deletes itself, and an incident produces exactly two notifications — opened and resolved — no matter how long it lasts. Genuinely urgent classes like malware warnings skip the confirmation round.

Screenshots on every alert

Opened alerts attach the exact screenshot from the failing run. The white page is proof, and proof is the difference between a client believing you and a client arguing with you.

Visual change detection

Each run is pixel-diffed against the previous one with a per-site threshold, because a homepage with a rotating hero and a checkout page have very different ideas of normal.

One operations dashboard

A React app combining network uptime from Kuma with browser health, incident history and authenticated screenshot access — the whole picture in one place.

Under the hood

How it's engineered

Detectors as pure functions

The browser gathers evidence once; detectors only interpret it — no I/O, no browser handle, no database. Every detector is unit-testable with a canned fixture, and adding one is a file, a registration line and a test. Incidents, alerting and per-site disabling work for it automatically.

Durable scheduling with the right failure semantics

Inngest fans out one deduplicated event per due site, at per-domain concurrency one. And a broken site is a successful run — it gathered evidence and recorded findings. Only infrastructure failures throw, because otherwise retries mask real incidents as platform noise in a dead-letter queue.

Only build the layer that didn't exist

Uptime Kuma answers "is the server reachable," Gatus answers "does HTTP behave," both untouched. My service answers the expensive question — does the page work in a browser — so the cheap checks stay cheap.

Metrics as columns, not blobs

Response time, DOM-ready, load time and error counts are first-class columns per run, which is what makes "this site has been getting slower for two weeks" a query instead of a project.

Where it landed

Outcomes

The gap is closed: the failures ping-based monitoring is blind to — malware interstitials, white screens, abandoned maintenance mode — now alert with the screenshot attached, and the two-notifications-per-incident rule means the channel stays worth listening to. The agency finds out before the client does, which is the entire point.

Two things are written down for next. The detector interface is synchronous, which was right for eighteen detectors reading local evidence and wrong for the ones I want next — Safe Browsing and certificate transparency are network calls that fit the seam conceptually but not the type signature. And the three-layer split is two extra things to operate: right for a self-hosted agency tool where Kuma and Gatus do real work for free, worth rethinking if the deployment target were someone else's infrastructure.