Humbear Performance Hub: one dashboard for ads, organic, and admissions
An internal analytics hub built in phases over three weeks: paid ads, short-form retention, and a lead-to-admission lifecycle, on one React app and one Postgres warehouse.
## the problem
Humbear Media's numbers lived in too many places. Meta Ads Manager for paid, platform analytics for short-form video, and the CRM for what actually matters in this vertical: did the lead become an admission. Answering a simple question like which creative pattern produces admissions, not clicks, meant three tabs and a spreadsheet.
The vertical adds a constraint most dashboards ignore. Treatment-center lead data is sensitive, so anything that touches the lead lifecycle has to work with de-identified records by design, not as an afterthought.
I did not want three tools. I wanted one internal hub where paid, organic, and lead lifecycle live as tabs on the same warehouse, so the joins between them, like cost per admission, are queries instead of exports.
## what I built
The Humbear Performance Hub: a single React dashboard with three products routed as tabs. /paid is a Meta Ads executive dashboard for the agency's treatment-center ad accounts. /organic is a Retention Ledger for short-form video, tracking how content actually holds attention. /leads is the Lead Lifecycle and Attribution view, following the ad-to-lead-to-admission funnel on de-identified records.
Underneath is one Neon Postgres warehouse with migrations in the repo, an AWS Lambda backend, and GitHub Actions ingestion runners that pull data in on a schedule. The frontend is React 18 with strict TypeScript, Vite, Tailwind, and Recharts, with a Vitest suite that stood at 109 passing tests at the end of the build.
It was built in named phases, and the commit log reads like the plan: Phase 0 restructured the hub, Phase 1 shipped the Retention Ledger, Phases 2 and 3 added Meta ingestion and creative pattern analysis, Phase 4 the lead lifecycle, Phase 5 paid-spend ingestion and the cost-per-admission join, and Phase 6 wired the CRM lifecycle from Dazos and the Humbear Platform. The repo started life inside my blog-automation repo and was split out when it outgrew it.
## how Claude was actually used
- 01
Phased builds instead of one heroic session
Each phase was its own Claude Code session with a defined deliverable, and each landed as a reviewable set of commits. The commit history runs July 13 to August 4, 2026 — 19 commits from hub restructure to CRM lifecycle. Working in phases meant every session started from a working system instead of a pile of intentions.
- 02
Tests as the contract between sessions
The Vitest suite grew phase over phase — the commit messages record it climbing from 71 to 74 to 109 passing tests. When a later session touched ingestion or the warehouse schema, the suite from earlier phases is what caught regressions. With multiple Claude sessions building on each other's work across weeks, the tests are the institutional memory.
- 03
Mock-first, with a deliberate seam to real data
The dashboard runs on a deterministic mock data provider by default — a spec of 10 clients, 50 campaigns, 150 ad sets, and 500 ads — behind the same interface as the real ingestion path. That is the same pattern that worked on Career That Lasts: the entire UI was built, reviewed, and tested against predictable data before any live API credentials entered the picture. It also means one thing worth saying plainly: any KPI you see in a screenshot of this dashboard is mock data unless I say otherwise.
- 04
De-identified lifecycle by design
The /leads product follows leads to admissions without carrying identifying detail into the warehouse. In this vertical that is the difference between a useful internal tool and a liability. The compliance posture was a requirement in the build plan, not a retrofit.
- 05
Different Claude models for different phases
The commit authorship is itself a receipt of how I work now: phases were built across sessions on different Claude models, including long-context sessions for the phases that had to hold the whole schema and ingestion pipeline in view at once.
## stack
## results (the verifiable kind)
- ✓One internal hub with three shipped products as tabs: /paid (Meta Ads executive view), /organic (short-form Retention Ledger), and /leads (lead lifecycle and attribution on de-identified records).
- ✓19 commits from July 13 to August 4, 2026, in six named phases, each phase a working increment.
- ✓A Vitest suite at 109 passing tests at the end of the build, grown phase over phase as the contract between sessions.
- ✓One Neon Postgres warehouse with in-repo migrations behind all three products, so cross-product joins like cost per admission are queries, not spreadsheet exports.
- ✓Deployed to Vercel with an AWS Lambda backend and GitHub Actions ingestion runners defined in the repo.
## what I learned
- →Phases beat marathons. Six named phases with their own sessions and commits produced a system I can reason about; the one-giant-commit approach I used on earlier projects produced systems I could only re-read.
- →A growing test suite is what makes multi-session agent work safe. Claude in session five does not remember session two; the 109 tests do.
- →Mock-first cuts both ways. It made the build fast and cheap, but it also means the dashboard can look finished while running on synthetic numbers. The discipline is refusing to quote any figure from it publicly until the real ingestion is confirmed live — which is exactly why no KPI from this dashboard appears on this page.
- →Splitting the repo out of blog-automation the moment it grew past a script was the right call. Repos that contain two unrelated systems produce commit histories that explain neither.
$ follow --the-build
Watch it happen, don't take my word for it
Every build on this site gets documented as it happens — the prompts, the dead ends, the results. No course at the end of this funnel. There is no funnel.
follow on x →