Engineering analytics for any public repo

Five views of a repository, and what each number actually counts

Paste a public GitHub repository and Repo Pulse gives you five views of it in one screen: how often it's committed to, who's doing the work, what it's written in, how much changed recently, and which files are absorbing that change.

Where the numbers come from

Everything comes from GitHub's public API. One request to this app fans out to GitHub for five sections of data, in parallel, and returns as a single response. Recent answers are reused for a short while so the same repository isn't re-fetched on every visit. Nothing about you is stored, and nothing is kept beyond that.

Commit activity
The last 52 weeks of commits, bucketed by week.
Contributors
The top contributors, ranked by commit count.
Languages
Bytes of code per language, which is how GitHub measures the mix.
Churn
The files absorbing the most change in the last 90 days, with lines added and removed.
Repository
The description, the default branch, and when it was last pushed to.

Each section carries its own success or failure. If GitHub rate-limits one of them, the other four still render: a partial dashboard rather than an error page.

What the numbers actually mean

Analytics tools tend to show you a number without saying what it counts. Four of these are worth being precise about.

~25%over git log

Commits in the last 90 days

Comes from GitHub's weekly activity statistics. That endpoint counts more than the commits sitting on the default branch. For React it reports about 25% more than git log on main would, and GitHub doesn't document what it includes. It's used here because the chart below it is drawn from the same data, and a headline that disagreed with the bars underneath it would be worse than one that's consistently approximate.

300hard ceiling

Files changed is capped

GitHub's comparison endpoint returns at most 300 files and gives no total, and paginating doesn't extend it. Any repo showing 300+ changed more than that, and the real figure isn't available.

0lockfiles ranked

Churn hotspots exclude lockfiles

package-lock.json, yarn.lock, Cargo.lock and their equivalents in a dozen other ecosystems. They are machine-written, and a single dependency update rewrites thousands of lines without anyone touching the project's own code. Left in, they top the ranking on almost every repo: on React, yarn.lock alone changed by more lines than any hand-written file. They are still counted in Files changed, because they did change. The two numbers measure different things on purpose.

90whole UTC days

The 90-day window

Ninety whole UTC days including today, not 90 × 24 hours from the moment you loaded the page. The current week in the chart is drawn as an open outline rather than a filled bar, because it hasn't finished yet; reading a Sunday-start week on a Monday isn't a drop in activity.

How it's built

A backend-for-frontend

The browser makes one request to this app's own API, which aggregates the five sections from GitHub and returns only the fields the screen uses. GitHub's raw responses are enormous (full commit objects with signatures and diff patches), and shipping them to the browser to discard 95% would be the easy, wrong choice.

One call for churn, not a hundred

“Which files changed in the last 90 days” naively means fetching every commit and then every commit's file list: over a hundred requests for an active repo, against a budget shared by every visitor. Instead it finds the commit at the 90-day boundary and makes a single comparison call against HEAD, which returns cumulative per-file statistics for the whole range.

No charting library

The commit chart is 52 elements sized by percentage. The language bar is the same idea horizontally. A charting dependency would have added weight to draw rectangles.

The look

The visual design is an homage to the GitHub Universe 2018 conference site, a fitting reference for a tool about GitHub repositories.

What carried over: the near-black blue-gray ground, the drifting starfield, the slow pulse on loading states, and the transparent input sitting on a single hairline rule with an arrow that slides on hover.

What didn't: Universe ran its gradient orange through coral to pink and filled its buttons with it. Here the gradient runs violet through pink to orange and appears as a 1px border, so every panel is outlined rather than flooded, which keeps the numbers, not the chrome, as the brightest thing on screen. Universe set its headings in uppercase with wide tracking; these are set tight and heavy, with the uppercase-and-spaced idiom kept only for the small labels above each panel.

Accessibility

Charts are where dashboards usually stop being accessible. Color carries the meaning, hover reveals the detail, and a screen reader gets a wall of empty divs.

Every bar names itself

Each column in the commit chart carries the week and the commit count as its accessible name, so the data is in the accessible tree rather than implied by height. The hover tooltip is a visual convenience on top of that, not the only way to read it.

The whole chart is also a table

One collapsible panel underneath gives every week and count as rows. It's one tab stop instead of fifty-two, and it's copyable, which the chart isn't.

Color is never the only signal

Every pair of colors that has to be told apart was checked under simulated color blindness rather than judged by eye. The two halves of the commit chart stay clearly distinct, as do the five language segments. Where a pair genuinely can't be separated, as with additions and deletions, the numbers always appear beside the bar.

Contrast is measured, not guessed

Every mark that carries data clears a 3:1 ratio against the background, including the deliberately recessive ones. The dimmed half of the commit chart looks better a shade fainter. It isn't, because that would drop it below the threshold.

Motion is optional

The drifting starfield, the loading pulse and the skeleton shimmer all stop for anyone whose system asks for reduced motion.

Keyboard focus is visible

Every link, button and input shows a clear focus ring, and the back link on the dashboard meets the minimum target size for touch.

What hasn't been checked

This section describes markup and measurement. It isn't a conformance claim: the site hasn't been through a screen reader, and heading order, the muted body text's contrast and the form's error announcement haven't been audited.

Trade-offs

Most of these had a defensible alternative. The reasoning matters more than the choice.

Fan out first, ask questions later

All five sections are requested from GitHub in parallel, and only then does the app check whether the repository exists. Checking first would save five requests when someone mistypes a name, but it would add a round trip to every successful load, to guard against the rarer case. Requests that wait on each other are the most common cause of a slow page, and a wasted request costs less than a slow one.

parallel fan-outoverexistence check first

Consistent over precise

The commits figure and the chart beneath it disagree with git log by about 25%, because GitHub's statistics endpoint counts more than the default branch. A different endpoint gives the exact number. Using it would mean a headline figure that contradicts the bars directly below it: a reader who added up the chart would find the tile wrong. Two sources that disagree on screen is worse than one source that's consistently approximate and labeled as such.

one consistent sourceoverthe exact commit count

Percentages that mean what you'd assume

The language breakdown shows the top five, but every percentage divides by all the bytes in the repo. So the five don't add to 100%, and a gray segment fills the difference. Dividing by just those five would make the bar fill neatly and the numbers total 100, at the cost of every percentage quietly overstating the language's real share.

divide by all bytesovera bar that fills to 100%

An accessible table instead of 52 tab stops

Making each bar keyboard-focusable would put the tooltip within reach of a keyboard, and drop 52 stops between one panel and the next. Instead every bar describes itself to a screen reader, and the whole chart is available as a table in one stop. Conformance isn't the same as usability.

one table stopover52 focusable bars

Time measured in durations, not calendar pages

“Last push 3 days ago” is computed from elapsed time, never by comparing month numbers. A push on 29 September read on 2 October is three days old, but it's also “a different month”, and logic built on that comparison would call it a month. That bug would be invisible for twenty-odd days at a stretch and then wrong for a few.

elapsed timeovercomparing month numbers

Caching before rate limiting

Every visitor shares one GitHub credential, so the question isn't whether someone could exhaust it but which tool to reach for. Responses are cached per repository, so the cost scales with how many different repos people look at rather than with traffic, and it handles the realistic case directly: someone impatiently reloading the same repo is served from cache and never touches GitHub. Per-IP rate limiting is the answer to someone hostile. Caching is the answer to someone impatient, and almost everyone is the second one.

per-repo cachingoverper-IP rate limiting

Cached at the request layer, not in the server's memory

There are two ways to cache this: hold the assembled answer in the running server's memory, or cache the outbound requests to GitHub themselves. This app runs serverless, where there is no single long-lived process: two people looking up the same repository a minute apart can be served by different machines, and anything one of them holds in memory is invisible to the other. An in-memory cache would look correct in development and quietly do nothing in production. Caching the requests instead puts them in Next.js's data cache rather than one machine's memory, and lets each GitHub endpoint carry its own lifetime: commit statistics are a weekly rollup and can sit still for an hour, while “last push” is the one number a reader would notice going stale, so it refreshes far sooner.

request-layer cacheoverin-process memory

REST, not GraphQL

GitHub offers both, and GraphQL would let these requests become one. For a project this size the setup (a client, a schema, generated types, a caching layer) costs more than it saves against a handful of well-understood REST endpoints that fan out in parallel anyway. The app's own API is REST for the same reason: one route handler, one shape, no build step.

parallel REST callsoverone GraphQL query

Counts and lists are different numbers, on purpose

GitHub hands out contributors one page at a time, so the people shown in the panel are the top few of the first page, while the count beside “Contributors” is the real total, read from a separate request rather than by counting the rows on screen. A dashboard that counted what it displayed would report 30 for almost every popular repository.

a separate count requestovercounting the rows shown

A panel that can't load says so

Each of the five sections is fetched separately and reports its own outcome, so one failure never blanks the page. They also distinguish between nothing to show and something went wrong: a repository younger than ninety days has no churn to report, which is a fact about the repository, while a request that failed is a fault and is shown as one.

per-section outcomesoverone page-level error

Limits worth knowing

  • Public repositories only. A private repo looks identical to one that doesn't exist, so you'll see the same message.
  • GitHub computes its activity statistics on demand. The first request for a rarely-visited repo can come back empty while that runs.
  • Language percentages are shares of bytes, not of files or lines, and vendored code counts.