Contributions

A year of GitHub activity, fetched server-side, with four distinct failures instead of one blank grid.

states
contributions, shown in its "ok" state

A year of public contributions as a grid of 53 columns, with a portalled tooltip on hover. The data is fetched on the server via the GraphQL API, so the grid is in the served HTML rather than behind a client fetch. That matters for a card that appears on two pages.

Four failures, not one null

The loader used to return null for four different things: a missing token, a non-200, a GraphQL error, and an account with nothing to show. The card had a single if (!data) branch covering all four, so a visitor was told "contributions unavailable" whether GitHub was unreachable or the year had genuinely been quiet.

Three of those are about the server and one is about the account. They are different sentences. The card can only pick the right one if the difference survives the return:

export async function getContributions(login: string): Promise<CardState<ContributionYear>> {
  if (!token) return { kind: "error", message: "GITHUB_TOKEN not set" }
  // …
  if (!calendar?.weeks?.length) return { kind: "empty" }
  return { kind: "ok", data: { weeks, total } }
}

One case does not look like a failure at all. GitHub reports a bad login or an exhausted quota as 200 plus an errors array, so checking res.ok misses it. It gets its own branch.

What never leaves the server

The message on an error state is for the logs, not the DOM. A thrown fetch error can carry the request that produced it, and that request carries the Authorization header. So what gets logged is err.message, never the error object, and never the response body, which echoes the query back.

Squaring off the grid

GitHub truncates the first week at its start and the last at its end, so padding goes on opposite sides:

const pad = Array.from({ length: 7 - days.length }, () => EMPTY)
return i === 0 ? [...pad, ...days] : [...days, ...pad]

Every column ends up with seven cells. That is what lets the grid be a plain flex row of columns instead of a real grid with placement rules.

The scale follows the theme

Every level is mixed from --fg-brand, so the calendar changes colour with the site's theme instead of being stuck on one accent:

color-mix(in srgb, var(--fg-brand) 50%, var(--bg-surface))
Active theme: entrepta, dark mode.