From b64249b86afa9ffcc39cc570d59011fdc664ed34 Mon Sep 17 00:00:00 2001
From: Dev Talan <84081651+devchaudhary24k@users.noreply.github.com>
Date: Sun, 4 Oct 2026 16:23:17 +0530
Subject: [PATCH 01/10] docs: plan Build Run 5 around people, Cards, and
comparisons
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Recenter the Site on Profiles and fair, hideable comparisons
- Define React-rendered Cards and per-person Surviving Lines
- Add ADRs 0020–0022 and update project context and state
---
CONTEXT.md | 72 ++-
IDEA.md | 419 +++++++++---------
STATE.md | 2 +-
docs/adr/0020-the-site-is-about-people.md | 33 ++
...eact-components-rendered-to-svg-and-png.md | 25 ++
...s-come-from-blame-per-person-on-request.md | 24 +
6 files changed, 357 insertions(+), 218 deletions(-)
create mode 100644 docs/adr/0020-the-site-is-about-people.md
create mode 100644 docs/adr/0021-cards-are-react-components-rendered-to-svg-and-png.md
create mode 100644 docs/adr/0022-surviving-lines-come-from-blame-per-person-on-request.md
diff --git a/CONTEXT.md b/CONTEXT.md
index 5f2cdc9..88e91a7 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -1,6 +1,6 @@
# commitscape
-A tool that reads a git repository, locally or on the Site, and reports the things about it that change what you do next. Every number it shows must either alter a decision or be interesting enough to screenshot, and every number must be enterable.
+A tool that shows a developer what they have built, how they stand next to the people they work with, and gives them something to share about it (ADR-0020). It reads git history, locally or on the Site, and GitHub's API. Every number says what it counts, and none is a guess.
## Language
@@ -44,7 +44,7 @@ _Avoid_: age, freshness, last modified.
**Code Age**:
The distribution of surviving code by the quarter in which it was introduced. Until line-level history exists it is measured per file: each file's lines count toward the quarter the file first appeared. Distinct from Staleness, which is per-file and backward-looking from now.
-_Avoid_: code lifetime, survival.
+_Avoid_: code lifetime, Surviving Lines (a different, line-level measure).
### What we measure it over
@@ -127,8 +127,12 @@ The code at HEAD drawn as nested rectangles, each as large as its lines of code,
_Avoid_: treemap, tree view, file browser.
**Card**:
-A composited image summarizing a repository, rendered for sharing rather than for reading in a terminal: the Overview's story of all of history, unless a shorter Window is asked for. The tool's growth mechanism, treated as a product feature.
-_Avoid_: summary image, badge (and report, which is the Report).
+One of a set of images made for sharing, about a person, a person in a repository, a Versus, a Race, a Season or a repository: one React component, shown on the Site, embedded in a README as animated SVG, and used as a PNG link preview (ADR-0021). The tool's growth mechanism, treated as a product feature.
+_Avoid_: summary image, badge, widget (and report, which is the Report).
+
+**Embed**:
+A Card placed in a README or elsewhere by its address, served from a stored copy that never waits on GitHub.
+_Avoid_: badge, widget, stats image.
**Report**:
Every answer the browser interface needs, for every Window, written ahead of time: it needs no server and answers only what it was written with. Kept as one file, stored on the Site for a repository, or uploaded as a Shared Report.
@@ -143,7 +147,7 @@ One short row per commit (who, when, its subject line, its Commit Kind, how much
_Avoid_: log, history, search index.
**Wrapped**:
-One person's year across every repository in a folder: only their own commits, under every address they commit with, told as a page and a Card.
+One person's year, told as a page and a set of Cards: across GitHub on the Site, or across every repository in a folder in the CLI, under every address they commit with.
_Avoid_: year in review, stats, recap.
**Moment**:
@@ -169,5 +173,59 @@ A repository someone let the Site read by choosing it in commitscape's GitHub Ap
_Avoid_: linked repo, imported repo, private repo.
**Leaderboard**:
-A ranking of repositories, never of people, by something commitscape measures, such as Bus Factor or how quickly issues get a first answer. It says when it was built and from how many repositories.
-_Avoid_: ranking of developers, top contributors, hall of fame.
+A ranking of repositories, or of people who haven't hidden, by one view at a time, such as Bus Factor or merged pull requests this Season. It says when it was built and from how many repositories.
+_Avoid_: score, top developers.
+
+### People
+
+**Profile**:
+Everything commitscape shows about one GitHub login: their totals, their Archetype and Achievements, and the per-project breakdown. GitHub's facts show at once; the engine's numbers stream in when their Builds finish. Private work appears only as totals unless its person opts in.
+_Avoid_: account, user page, dashboard.
+
+**Hidden**:
+Said of a Profile whose person chose to stay out of comparisons: it appears in no Versus, Leaderboard, Race or other person's Standings.
+_Avoid_: private profile, opted out.
+
+**Surviving Lines**:
+The lines at a repository's head that blame attributes to a person, passing through Bulk Commits and ignored revisions, leaving out Generated Files and Prose Files (ADR-0022). Counted per person on request; "not counted" when over its time budget.
+_Avoid_: lines owned, code alive, impact.
+
+**Survival**:
+A person's Surviving Lines as a share of the lines they added in that repository. Shown only when both are known.
+_Avoid_: retention, survival rate, code quality.
+
+**Standing**:
+Where a person stands among a repository's people in one view, counted from the top, as Rank is for files. **Standings** are every person's Standing side by side, view by view, never combined into a score. A private repository's Standings are shown only to people who can see it.
+_Avoid_: score, rating, leaderboard position.
+
+**Proof of Work**:
+What a person shipped in a chosen period, grouped by repository and month, with links: merged pull requests and commits. Shared as a link or downloaded as Markdown or PDF.
+_Avoid_: report (that is the Report), timesheet, invoice.
+
+**Versus**:
+Two Profiles side by side, with a winner for each view and no overall winner.
+_Avoid_: battle, duel, comparison score.
+
+**Rival**:
+A person someone chose to measure themselves against, whose gap shows on their own Profile.
+_Avoid_: friend, follow.
+
+**Archetype**:
+A label for how a person works, from a written rule over their numbers, such as Reviewer (more reviews than pull requests) or Janitor (removes more lines than they add). Its rule is shown with it.
+_Avoid_: personality, type, persona.
+
+**Achievement**:
+A milestone a person reached, from a written rule, such as a first merged pull request in a repository with 10,000 stars. Each has a Card.
+_Avoid_: badge, trophy.
+
+**Season**:
+One calendar month, after which Crew Standings and people Leaderboards start again.
+_Avoid_: period, sprint.
+
+**Race**:
+A fixed window in which two or more people who accepted compare their Standings live, ending with a finish Card.
+_Avoid_: challenge, competition, contest.
+
+**Crew**:
+A group of people who accepted an invitation to compare themselves every Season.
+_Avoid_: team, group, organisation.
diff --git a/IDEA.md b/IDEA.md
index 363d1ff..9c8feb3 100644
--- a/IDEA.md
+++ b/IDEA.md
@@ -1,258 +1,257 @@
# commitscape: the idea
-Written 2026-09-25, after Build Run 3 shipped and the repository went on
-GitHub (`pixelactstudio/commitscape`). This is the product brief for Build Run
-4. It says what commitscape becomes, what changes, and in what order.
-`STATE.md` tracks progress against it; `CONTEXT.md` is the glossary;
-`docs/adr/` holds the decisions that are hard to reverse. Build Run 3's brief
-is in git history (`git show ed40d28:IDEA.md`).
+Written 2026-10-04, after the owner used the live Site
+(`commitscape.damnlabs.com`) on their own repositories and on facebook/react
+and reviewed it. This is the product brief for Build Run 5. It says what
+commitscape becomes, what changes, and in what order. `STATE.md` tracks
+progress against it; `CONTEXT.md` is the glossary; `docs/adr/` holds the
+decisions that are hard to reverse. Build Run 4's brief is in git history
+(`git show e54e961:IDEA.md`).
---
## In one sentence
-Run one command on any git repository, or paste any GitHub link into the
-Site, and see its story: who built it, who knows which part, what is fragile,
-what changes together, and what you are about to forget. Every page is good
-enough to screenshot.
+commitscape shows a developer what they have built, how they stand next to
+the people they work with, and gives them something good to post about it.
+
+## Why it changes
+
+commitscape started from one question: how much work have I put into this
+project? The answer grew into a Report about a repository, and the Report
+answers questions an auditor asks (what is fragile, who holds which folder,
+what changes together), not the ones a developer arrives with. The owner's
+review of 2026-10-04 found:
+
+- **Nothing on a repository page is about the person looking at it.** They
+ look, think "neat", and leave, with nothing to come back for and nothing
+ of theirs to share.
+- **Git alone misses where the work is.** Most work is pull requests and
+ reviews, and most repositories squash-merge, so commit counts say little.
+ Kinds of work is empty wherever commits follow no convention, and the
+ People table is mostly dashes.
+- **Several panels mean nothing to a visitor:** the story so far (a list of
+ first and last commits and releases), Did you know, Worth a look, Risk.
+- **Repository analytics is already free elsewhere** (GitHub Insights, OSS
+ Insight, Repobeats, star-history), often better presented.
+- **The interface feels slow and unfinished:** pages load grey and then turn
+ blue, skeletons replace whole pages instead of the missing part, the main
+ chart draws twice, release lines bury the chart on large repositories,
+ people have no avatars, and the logo is not the favicon or used anywhere
+ on the Site.
+
+Developer tools that reach tens of thousands of people share three things:
+they are about *me*, they make something I post or embed (every embedded
+README card is an advert), and they need nothing but a link. Build Run 5
+turns commitscape around to the person.
## Who it is for
-- **A developer joining a codebase** who needs to know who to ask and where the
- risk is.
-- **A developer about to push** who wants to know what they forgot to change.
-- **A maintainer or lead** who wants to see who holds which part of the code
- before someone leaves.
-- **Anyone choosing a dependency** who wants to know whether it is alive and
- whether it depends on one person.
-- **Anyone curious about their own work**, at the end of the year or before a
- review.
-- **Anyone on a headless machine** who wants to see all of this in the
- laptop's browser without setting up Tailscale or port forwarding.
+- **A developer proving their work:** a freelancer showing a client what
+ they shipped this month, anyone writing a self-review, anyone posting on
+ LinkedIn or X about what they built.
+- **A developer on a team who likes to stay ahead:** they want to see how
+ they stand among the 23 people on their project, fairly.
+- **Friends who compete:** versus pages, races and crews.
+- **Anyone with a GitHub profile README** who wants a better card on it.
+- **A maintainer** who wants to thank contributors with a hall of fame.
+- Still: **anyone curious about a repository**, through the trimmed
+ repository page, and anyone using the CLI locally.
## What it must stay
-- **Small.** One person maintains it. Every feature is useful, shareable, or
- both.
-- **Local first.** The CLI and its local browser interface stay complete
- without the Site. Nothing about a repository leaves the machine unless the
- user runs `commitscape share` (encrypted, ADR-0016) or asks the Site about
- a repository on GitHub.
+- **Small.** One person maintains it. Every feature is about a person and
+ makes something worth sharing, or it goes.
- **Honest.** No number the data cannot support. "Unknown" is never 0. A
- limitation is stated next to the number it affects ("lines not counted").
-- **Plain.** Every number in words a newcomer understands; `?` explains how
- it was measured.
-- **Fast.** Terminal UI first screen under 100 ms warm (ADR-0002). Local
- browser interface usable within a second warm. On the Site, a stored Report
- shows within a second, and commit search updates within a frame.
-- **Free to run.** The Site fits Cloudflare's free Workers plan (ADR-0014). The
- only other cost is the owner's existing VPS (ADR-0015).
-- **Open source, all of it.** The CLI, the Site and the Builder live in this
- repository under MIT OR Apache-2.0.
-- **Built openly with AI.** That is how the project is made, not a feature of
- the product. The product shows nothing AI-related.
-- **No scores for people.** No productivity score and no ranking of
- developers. Leaderboards rank repositories.
+ limitation is stated next to the number it affects.
+- **Fair.** Standings rank by what is hard to inflate (Surviving Lines,
+ merged pull requests, reviews) before what is easy (commits, raw lines).
+ Bulk Commits, Generated Files and `.git-blame-ignore-revs` never count.
+- **Private by default where it matters** (ADR-0020): private work is never
+ named to someone who can't see it, and anyone can hide from comparisons.
+- **Server-rendered and streamed.** Every page is drawn on the server with
+ the data it has. Each slow part sits in its own Suspense boundary with a
+ skeleton of its final size, so nothing moves when data arrives. GitHub's
+ facts show at once; the engine's numbers stream in after.
+- **Plain.** Every number in words a newcomer understands, with what it
+ counts ("60 commits", never a bare "60").
+- **Open source, all of it,** under MIT OR Apache-2.0.
+- **Built openly with AI.** That is how the project is made, not a feature.
+ The product shows nothing AI-related.
---
-## What changed since Build Run 3
+## What changed since Build Run 4
-The owner reviewed the finished Build Run 3 on 2026-09-25 and decided to grow
-commitscape into something with a future, while keeping the local tool as the
-heart of it. The following Build Run 3 decisions are **reversed**:
-
-| Build Run 3 said | Build Run 4 says |
+| Build Run 4 said | Build Run 5 says |
|---|---|
-| No hosted service, no accounts, no uploading | A hosted Site with GitHub sign-in; uploading only as an encrypted Shared Report or when the user asks the Site about a GitHub repository |
-| No Next.js, TanStack Start or Node server | The Site is TanStack Start on Cloudflare Workers. The local interface is still served by the binary (ADR-0010), and Rust never runs as a web server (ADR-0014) |
-| The web UI is hand-rolled CSS | The web UI is built on Astryx (ADR-0018) |
-| `web/` is the web app | A monorepo: `apps/local`, `apps/site`, `apps/builder`, `packages/ui`, `packages/data` (ADR-0013) |
-| No HTTP client or TLS in the binary (ADR-0009) | `share` uploads over HTTPS (ADR-0016); GitHub is still asked through `gh` |
-
-Kept: the terminal UI stays **frozen** (bug fixes only). All interface work
-goes to the browser interface, which the local page and the Site share.
-
----
+| A repository is the centre of everything | A person is the centre; the repository page is secondary (ADR-0020) |
+| No scores or rankings of people; Leaderboards rank repositories | People are compared, within a repository and with each other, by several views shown side by side, never one combined score. Anyone can hide from comparisons (ADR-0020) |
+| The Card is one composited image of a repository | Cards are a set of React components, rendered as animated SVG for READMEs and PNG for previews (ADR-0021) |
+| Code Age is per file; blame-based surviving lines are "later" | Surviving Lines per person, from blame, are a headline number (ADR-0022) |
+| Hosted Reports have no pull-request history | The Site reads pull requests and reviews from GitHub's API for profiles, and the Builder keeps each repository's pull requests (ADR-0020) |
-## Three ways to use it
-
-1. **Locally.** `commitscape` in a repository opens the browser interface
- served by the binary, or the terminal UI where no browser can open. As
- today, plus the Astryx interface, a Commits screen and a Share button.
-2. **Share from a terminal.** `commitscape share` builds the Report locally,
- encrypts it, uploads it, prints a link and **exits**. The terminal is free
- at once. The link works on any browser for 4 hours (1 to 12 with
- `--expires`). The page has a Delete button. The Site can't read what it
- stores (ADR-0016).
-3. **On the Site.**
- - **Any public GitHub repository:** paste a link (or go to
- `/gh//`). GitHub's facts show at once; the full Report
- follows when the Builder has read the history (seconds for most, about
- 15 s for a React-sized one the first time), and is instant for everyone
- after.
- - **Your repositories:** sign in with GitHub, pick repositories through
- the GitHub App (read-only, ADR-0017), and open any of them.
- - **Leaderboards:** repositories ranked by what commitscape measures.
-
-### How the pieces fit
-
-```
- Browser ──────────────► Site: TanStack Start on Cloudflare Workers (free plan)
- │ pages: static assets
- │ decrypts Shared /api/*: auth, Reports, Shares, Builds, webhooks
- │ Reports; searches D1: users, sessions, repositories, Builds, Shares
- │ commits R2: Reports (gzip), encrypted Shared Reports
- │ │ a Build (HTTPS + HMAC)
- │ ▼
- │ Builder on the owner's VPS: TypeScript job runner
- │ git clone → `commitscape report --data` → the Site → R2
- │
- A user's machine: `commitscape` (local page, TUI, `share`, `check`, …)
-```
-
-Every screen, in all three ways, is the same code (`packages/ui`), reading
-through a Data Source (ADR-0013): the local server, an inlined Report, or a
-fetched Report.
+Kept: the Rust engine, identity merging (ADR-0011), the Builder and its
+queue, R2, Postgres, GitHub sign-in through the App (ADR-0017), Shared
+Reports, the CLI, `npx commitscape`, and the MCP server. The terminal UI
+stays frozen.
---
-## The browser interface, on Astryx
-
-- **Astryx for every component** (ADR-0018): app shell and top navigation,
- tabs, tables for people, files and hotspots, tooltips and hover cards,
- dialogs, toasts, avatars, a ⌘K command palette ("Jump to": a screen, a
- person, a folder, a file, a commit). One commitscape theme in light and
- dark, plus "system".
-- **Charts stay ours,** restyled with Astryx's tokens. Astryx's canary
- charts replace bar or line charts only if a side-by-side screenshot shows
- they're better; Recharts is the fallback.
-- **Keyboard first,** as npmx.dev does it: `/` focuses search, `1`–`6` pick a
- screen, `?` shows every shortcut and highlights the keys on screen,
- shortcuts off while typing.
-- **Avatars.** People linked to a GitHub login show their GitHub avatar.
- Locally the browser fetches it from GitHub unless `--offline`. README's
- "What leaves your machine" says so.
-- **Six screens:** Overview, Activity, People, Map, Risk and **Commits**
- (ADR-0019): search every commit by message, person, date and kind,
- instantly, with a link to GitHub. The terminal UI doesn't get it.
-- **Share button** on the local page: the same as `commitscape share`, with
- the link to copy.
-
## The Site
-Few pages, each doing one thing well. npmx.dev is the reference for feel:
-a big search box, instant response, keyboard shortcuts, calm layout.
-
| Page | What it is |
|---|---|
-| `/` | Landing page: one line on what commitscape is; a box to paste a GitHub link (with suggestions); the three ways to use it, each with its command or button; a few leaderboard highlights; install commands |
-| `/gh//` | A repository's Report: the six screens, with GitHub's instant facts first and "updating" while a newer Build runs. Private ones only for signed-in users who can see them on GitHub |
-| `/s/#` | A Shared Report, decrypted in the browser, with its expiry and a Delete button |
-| `/me` | Signed in: your repositories through the GitHub App, "Add repositories", "Delete my data" |
-| `/leaderboards` | Repository rankings, rebuilt daily as static pages |
-| `/privacy` | Exactly what is stored, where, for how long, and who can read it |
-
-The top navigation has the command palette and a **Connect** menu:
-"Share from your terminal" (shows the `share` command) and "Sign in with
-GitHub".
-
-**Social previews.** Every repository page has an Open Graph image: its
-Card, written as a PNG or SVG when its Report is built and served from R2.
-
-## Leaderboards
-
-Repositories only, never people. Built by the Builder from a seed list of
-popular repositories per language (from GitHub search: most stars), a
-budgeted number per night, so the VPS isn't overrun. First boards:
-
-- **Resting on one person:** popular projects with a Bus Factor of 1.
-- **Most maintainers active** in the last 90 days.
-- **Most active this month** by commits and by people.
-- **Fastest to answer issues** (from `health`'s numbers).
-- **Oldest code still running:** the largest share of lines untouched for five
- years.
-
-Each row links to the repository's page. Boards say when they were built and
-from how many repositories.
-
-## Security, in one place
-
-- **Shared Reports:** AES-256-GCM, key only in the link's fragment, removed
- from the address bar on load; Delete Token derived from the key and stored
- hashed; IDs of 128 random bits; 4 hours by default, 12 at most; 25 MB cap;
- per-IP limits (ADR-0016).
-- **GitHub:** one GitHub App, read-only permissions; installation tokens live
- an hour and are never stored; access re-checked on every view; webhooks
- verified; uninstall and "Delete my data" delete everything (ADR-0017).
-- **Builder:** HMAC-authenticated Builds, one at a time, size and time caps,
- clones deleted after private Builds (ADR-0015).
-- **Hosted Commit Lists carry no email addresses** (ADR-0019).
-- **The local server** keeps ADR-0010's token and `Host` checks.
-
-## The name
-
-`commitscape` may be renamed before the first npm release. Nothing is
-published and no domain is bought during this build run. The product name
-and the Site's origin each live in one constant in `packages/data` (ADR-0014);
-the CLI's Site origin is overridable with `COMMITSCAPE_SITE`. Code and docs
-use `commitscape` until the owner decides.
+| `/` | One line on what commitscape is, a box that takes a GitHub username or a repository link, "See your own" with sign-in, example profiles and cards, install commands |
+| `/u/` | **The Profile.** GitHub identity and avatar; totals (pull requests opened, merged and closed, reviews given, median time to merge, commits, lines added and removed, Surviving Lines, active days, longest streak); the Archetype and Achievements; the per-project breakdown; activity over time; languages over the years; the people they work with most |
+| `/u///` | **You in this repository.** Your numbers first, then your Standing, then the Standings of everyone in it, each with avatar, pull requests, reviews, lines and Surviving Lines, and what each person works on |
+| `/u//cards` | **The card gallery.** Every Card for this person, in light and dark, with the README Markdown to copy, a PNG to download, and share to LinkedIn and X |
+| `/u//work` | **Proof of Work.** Everything shipped in a chosen period, grouped by repository and month, with links; a link to share and Markdown and PDF to download |
+| `/vs//` | **Versus.** Two Profiles side by side, a winner per view, with its own Card |
+| `/races/` | **A Race.** A fixed window, two or more people who agreed to it, live Standings, a finish Card |
+| `/crews/` | **A Crew.** A group that compares itself every Season |
+| `/gh//` | **The repository page,** trimmed (below), with contributors shown as people with avatars |
+| `/leaderboards` | Repositories as before, plus people: top contributors this month, per repository, with a time filter. Only people who haven't hidden |
+| `/me` | Settings: Connected Repositories, Rivals, Crews, what is shown and hidden, "Delete my data" |
+| `/s/#` | A Shared Report, unchanged |
+| `/privacy` | Rewritten for Profiles, comparisons and Cards |
+
+### Two speeds
+
+A Profile has two sources (ADR-0020):
+1. **GitHub's API, at once:** identity, avatar, repositories, pull requests
+ with their additions and deletions, reviews, issues, contribution
+ calendar. Read with the signed-in viewer's own token; anonymous views
+ use the Site's token and a stored copy.
+2. **The engine, streamed in:** for each repository the person works on that
+ the Site has built, their Lines Changed and Surviving Lines, under every
+ address they commit with. Each section shows "counting…" in its own
+ skeleton until its Build finishes, then fills in.
+
+### The repository page, trimmed
+
+- **Kept:** the stat tiles; one chart of commits over time with releases
+ drawn only where they fit (the story so far merges into it); contributors
+ with avatars, pull requests and +/− lines; Languages; Commits with
+ avatars; the Map, as something fun to explore.
+- **Removed from the Site:** the story so far as a list, Did you know, Worth
+ a look, Risk. Risk stays in the CLI and the MCP server, where an agent or
+ a lead asks for it.
+- **Hidden when empty:** Kinds of work in a repository with no commit
+ convention, and any column that would be all dashes.
+- **Search** across the page: people, files, commits.
+
+## Cards
+
+A Card is one React component rendered three ways (ADR-0021): on the Site
+as itself, as **SVG** for README embeds (animated: numbers count up, bars
+grow, the calendar fills), and as **PNG** for link previews and downloads.
+Every page has a dynamic preview image, like GitHub's, from a Card.
+
+The set, each in light and dark:
+- **Totals:** pull requests, reviews, lines, Surviving Lines.
+- **Top repositories:** where the person's work is, with their Standing in
+ each.
+- **Survival:** "41k of the 120k lines I wrote still run."
+- **Calendar:** the contribution calendar, animated.
+- **Languages over the years.**
+- **Archetype** and **Achievements.**
+- **Person in a repository:** "Top 3% of facebook/react contributors."
+- **Versus**, **Race finish**, **Season recap**.
+- **Hall of fame** for maintainers: a repository's contributors with avatars
+ and their numbers.
+- **Proof of Work:** a period's summary.
+
+Embeds are served from a stored copy, refreshed in the background at most
+every six hours, so a popular README never reaches GitHub's API.
+
+## The fun side
+
+- **Versus:** any two public Profiles, a winner per view, no overall score.
+- **Rivals:** pick a person; your Profile always shows the gap ("312
+ Surviving Lines behind this month").
+- **Archetypes:** a label from plain rules over the numbers, no AI: Builder,
+ Reviewer, Janitor (removes more than adds), Night Owl, Firefighter (mostly
+ fixes), Polyglot, and a few more, each with its rule shown.
+- **Achievements:** "First pull request merged into a 10k-star repository",
+ "100 reviews", "A line that has survived five years", "Removed 10k lines in
+ one pull request". Each has a Card.
+- **Seasons:** Standings in a Crew and on the people boards reset monthly so
+ a newcomer can win; each Season ends with a recap Card.
+- **Races:** a fixed window, two or more people who each accepted, live
+ Standings, a finish Card.
+- **Crews:** a group of friends or a team with shared Standings each Season.
+ Joining is by invitation, accepted.
+
+## Proof of Work
+
+The original problem, answered directly: pick a period, optionally a
+repository or a client's organisation, and get every merged pull request and
+commit in it, grouped by repository and month, with sizes and links. Share it
+as a link, or download Markdown or a PDF for a client or a self-review.
+Private work appears only to the signed-in person, and in what they choose
+to share.
+
+## What to watch
+
+- **Privacy** (ADR-0020). A private repository's Standings are shown only to
+ people who can see it on GitHub. A Profile shows private work as totals,
+ never by name, unless the person opts in. A signed-in person can hide
+ their Profile from Versus, Leaderboards and other people's Standings.
+ Races and Crews need each person's acceptance.
+- **Cost of Surviving Lines** (ADR-0022). Blame is expensive on huge
+ repositories, so it runs per person, only over the files they changed,
+ when someone asks, and is kept per repository head. Over a time budget it
+ says "not counted", never a guess.
+- **GitHub's rate limits** (ADR-0020). The viewer's own token where there is
+ one; stored copies everywhere else; embeds never call GitHub.
+- **Gaming.** Standings lead with Surviving Lines and merged pull requests.
+ Raw commits and raw lines are shown, never ranked first.
+- **The name.** commitscape still fits. It is not changed in this run.
---
-## Build Run 4: the plan
+## Build Run 5: the plan
-Phases continue from Build Run 3. Each phase ends with every check passing
-(Rust on Linux, macOS and Windows; the TypeScript workspace; Playwright) and
-`STATE.md` updated with its measured numbers and findings. **The agent
+Phases continue from Build Run 4. Each phase ends with every check passing
+(Rust on Linux, macOS and Windows; the TypeScript workspace; Playwright)
+and `STATE.md` updated with its measured numbers and findings. **The agent
doing the work never commits, pushes, publishes or deploys; the owner
commits.**
| Phase | What | Gate |
|---|---|---|
-| 23 | Monorepo (ADR-0013): pnpm + Turborepo; `web/` → `apps/local`; `packages/ui` and `packages/data`; the Data Source seam; every path that named `web/` (build.rs, assets.rs, flake, CI, release, scripts, README) | Every existing test passes from the new layout (Rust, vitest, Playwright); `cargo build` embeds the app; `nix build` builds; actionlint passes on both workflows |
-| 24 | Astryx (ADR-0018): theme, app shell, every screen's components, command palette, keyboard shortcuts, avatars; charts restyled; canary charts tried side by side | Playwright tests pass; screenshots of every screen in both themes on ripgrep in `target/preview/web/`; size of the embedded app before and after; a keyboard-only walk through every screen |
-| 25 | Commit search (ADR-0019): subjects in the index (cache version bump), `/api/commits`, the Commit List in Reports, the Commits screen | Hand-worked fixture values; goldens and the generated types updated; search time and list size measured on facebook/react and rust-lang/rust |
-| 26 | The Site's foundation (ADR-0014): `apps/site`, landing page, repository page reading a stored Report, the fetched-Report Data Source, D1 schema and migrations, rate limiting, `/privacy` | Under `wrangler dev`, Playwright passes on the landing page and on a repository page from a fixture's Report; CPU time of every API handler recorded (under 10 ms) |
-| 27 | Builder and public lookup (ADR-0015): `commitscape report --data`, clone policy and threshold, `apps/builder`, instant GitHub facts, refresh after 24 hours, Open Graph Cards | End to end on this machine (`wrangler dev` + the Builder): ripgrep and facebook/react built and shown, times recorded; not-found, private, too-big and timed-out repositories each show a plain message |
-| 28 | Sharing (ADR-0016): `commitscape share` (with `--expires`, `--delete`, `--list`, `--yes`), the Share button, the `/s/` page, expiry via Cron Trigger | End to end on this machine, CLI to browser; the key never appears in any request (checked in Playwright); a tampered ciphertext fails; expired answers 410; CI green on all three operating systems with the new crates |
-| 29 | GitHub sign-in (ADR-0017): the App's user sign-in, `/me`, installations, access checks, webhooks, retention, "Delete my data" | Tests against GitHub responses recorded by hand (as `commitscape-forge` does) and, if the owner has created a test App, against GitHub itself; webhook signatures and access checks covered |
-| 30 | Leaderboards: seed list, nightly budget, the five boards as static pages | 50 or more seed repositories built on this machine; boards rendered and screenshotted |
-| 31 | Ready to launch: README for all three ways, "What leaves your machine" rewritten, `DEPLOY.md` (Cloudflare, the VPS, the GitHub App, secrets), a security pass over every endpoint | Every CI job green; `DEPLOY.md` followed from scratch against local stand-ins; nothing is left for the owner but the steps listed under "Needs the owner" |
+| 32 | Foundation: server rendering with a Suspense boundary and sized skeleton per section; the theme applied on the server (no grey flash); the logo as favicon and in the header; the chart drawn once; releases thinned on dense charts; the repository page trimmed as above, with avatars and search | Playwright on every page; Cumulative Layout Shift under 0.05 on each, measured; screenshots in both themes; no request after hydration repeats one the server made |
+| 33 | The Profile, first speed (ADR-0020): `/u/` from GitHub's API, stored copies in Postgres, the viewer's token, the per-project breakdown, the new landing page | Tests against GitHub responses written by hand; the owner's Profile and two well-known ones rendered and screenshotted; GitHub API calls per view counted and recorded |
+| 34 | The Profile, second speed (ADR-0022): Surviving Lines in the engine, per person and per repository head; the Builder computes them on request; Lines Changed per person; sections stream in | Hand-worked fixture values for Surviving Lines, Bulk Commits and ignored revisions; time measured on ripgrep, facebook/react and rust-lang/rust; over budget shows "not counted" |
+| 35 | You in this repository: `/u///`, Standings, the Builder keeping each repository's pull requests and reviews, privacy rules and hiding, `/privacy` rewritten, MCP tools for a Profile and Standings | Access tests: a private repository's Standings refused to someone without access; a hidden person absent everywhere; pull-request fetch time on facebook/react recorded |
+| 36 | Cards (ADR-0021): the rendering pipeline, the set above, animation, light and dark, the gallery, embeds with stored copies, a preview image on every page | Every Card snapshotted as SVG and PNG; an embed shown in a real README on GitHub (owner's test repository); render time per Card under 200 ms, measured |
+| 37 | Proof of Work: `/u//work`, filters, share link, Markdown and PDF | A month of the owner's work checked against GitHub by hand; private items absent from a shared link unless chosen |
+| 38 | Versus and Rivals | Fixture Profiles give the winners worked out by hand; a hidden Profile refused |
+| 39 | Archetypes and Achievements, each rule written in `CONTEXT.md` and on the page | Every rule tested at its edges on fixtures |
+| 40 | Seasons, Races and Crews: invitations, acceptance, live Standings, recap and finish Cards | End to end with three test accounts; leaving a Crew removes the person from it at once |
+| 41 | People on the Leaderboards: top contributors this month, per repository, with a time filter and Seasons | Boards built from the seed repositories and screenshotted; hidden people absent |
+| 42 | Wrapped 2026: a person's year across GitHub, as a page and a set of Cards | The owner's Wrapped checked against GitHub by hand |
### Needs the owner
-These can't be done by an agent and aren't part of any gate:
-- choosing the name, buying the domain, publishing to npm and Homebrew
-- creating the Cloudflare account, D1 database, R2 bucket and secrets, and
- deploying
-- setting up the VPS from `DEPLOY.md`
-- creating the GitHub App (a test one earlier helps Phase 29)
-- GitHub Sponsors (`.github/FUNDING.yml` once the account exists)
+- Adding the "Read user profile" permission to the GitHub App if Phase 33
+ finds it needed (it is read-only).
+- A test repository on GitHub to show an embed in a real README (Phase 36).
+- Two more GitHub accounts, or friends, to test Races and Crews (Phase 40).
+- Deploying each phase when they choose.
## Later, not in this run
-- A live mode for Shared Reports, if people ask for it.
-- A paid team plan: private-repository dashboards, weekly emails ("the only
- person who knows `payments/` hasn't committed in 60 days"), `check` as a
- bot on private pull requests. Public repositories stay free.
-- Sponsor slots on the landing page and leaderboards, once there is traffic.
-- `commitscape ssh host:path`, the replay video, GitLab, blame-based
- surviving lines, replacing `bincode`.
-
-## Launch
-
-Unchanged in spirit: aim for early December 2026, Wrapped season. The post
-shows the owner's Wrapped, a repository card, and the Site's "paste any
-GitHub link", and says plainly that the project was built with AI agents.
+- Notifications for Rivals, Races and Crews (email, then maybe push).
+- A paid plan for agencies: Proof of Work across a team, for clients.
+- GitLab.
## Not doing
-- A live relay or tunnel between a user's machine and the Site (ADR-0016).
-- A Rust web server anywhere (ADR-0014).
-- Anything that needs Cloudflare's paid plan.
-- Scores or rankings of people.
-- AI attribution of any kind.
+- One combined score for a person.
+- Anything AI in the product.
- New features in the terminal UI.
- Email addresses on the Site.
+- Renaming the project in this run.
- Publishing, deploying or buying anything during the build run.
diff --git a/STATE.md b/STATE.md
index e21bc95..9c35afc 100644
--- a/STATE.md
+++ b/STATE.md
@@ -3,7 +3,7 @@
Running log for Build Run 1 (Phases 0 to 7). Written so a fresh session with
no context can read this plus `docs/adr/` and continue without asking anything.
-**Current position:** Build Run 4 (Phases 23 to 31) is done, all of it uncommitted in the working tree: its brief is `IDEA.md`, with ADR-0013 to ADR-0019. Every phase's findings and the files it created, changed or deleted are below. Build Run 3 is done (Phases 13 to 22). The repository is on GitHub (`pixelactstudio/commitscape`) and CI is green on Linux, macOS and Windows for what is committed. Nothing is published or deployed: the name may still change.
+**Current position:** Build Run 5 (Phases 32 to 42) is planned and not started: its brief is `IDEA.md`, with ADR-0020 to ADR-0022, written 2026-10-04 after the owner's review of the live Site. Build Run 4 (Phases 23 to 31) is done, all of it uncommitted in the working tree: its brief is `IDEA.md`, with ADR-0013 to ADR-0019. Every phase's findings and the files it created, changed or deleted are below. Build Run 3 is done (Phases 13 to 22). The repository is on GitHub (`pixelactstudio/commitscape`) and CI is green on Linux, macOS and Windows for what is committed. Nothing is published or deployed: the name may still change.
Build Run 1 (Phases 0 to 7) built a correct, fast tool. Build Run 2 (Phases 8
to 12) made it fun and visual. On 2026-09-24 the owner used it on their own
repositories and reviewed it. The review and the decisions that followed are
diff --git a/docs/adr/0020-the-site-is-about-people.md b/docs/adr/0020-the-site-is-about-people.md
new file mode 100644
index 0000000..768b5f3
--- /dev/null
+++ b/docs/adr/0020-the-site-is-about-people.md
@@ -0,0 +1,33 @@
+# The Site is about people; people are compared, never scored, and can hide
+
+The Site's centre moves from a repository's Report to a person's Profile. People are compared within a repository and with each other, view by view, with no combined score. Private work is never named to someone who can't see it, and anyone can hide from comparisons. A Profile reads GitHub's API at once and the engine's numbers stream in after.
+
+## Status
+
+accepted (2026-10-04, Build Run 5 plan). Reverses Build Run 4's "No scores for people. Leaderboards rank repositories." Extends ADR-0017.
+
+## Context
+
+The owner reviewed the live Site on 2026-10-04. A repository's Report answers an auditor's questions, and nothing on it is about the visitor, so nobody returns or shares it. The owner's original question was "how much have I put into this project", and they compare themselves with the people on their team to stay ahead. Developer tools that spread are about the person and make something they post.
+
+Ranking people carries risks: shaming, gaming, and naming private work. GitHub's API also has rate limits: 5,000 requests an hour per token.
+
+## Decision
+
+- **A Profile per GitHub login** (`/u/`), built from public data for anyone, and from private data only for the signed-in person themselves.
+- **Two speeds.** GitHub's GraphQL API gives identity, repositories, pull requests with additions and deletions, reviews, issues and the contribution calendar at once. The engine gives Lines Changed and Surviving Lines (ADR-0022) for repositories the Site has built, streamed into their sections.
+- **Tokens.** A signed-in viewer's requests use their own token. Anonymous views use the Site's token and a stored copy in Postgres, refreshed after a day. Embeds and preview images never call GitHub (ADR-0021).
+- **Comparisons, not scores.** Standings, Versus, Races, Crews and the people Leaderboards show several views side by side: merged pull requests, reviews, Surviving Lines, Lines Changed, commits. They lead with the views that are hard to inflate. There is never one number for a person.
+- **Privacy rules:**
+ - A private repository's Standings are shown only to people who can see the repository on GitHub, checked as ADR-0017 checks access.
+ - A Profile counts private work only in totals and never names a private repository, unless its person opts in.
+ - A signed-in person can hide their Profile. A hidden person appears in no Versus, Leaderboard or other person's Standings, and their Profile says only that it is hidden.
+ - Races and Crews include a person only after they accept.
+- **The Builder keeps each built repository's pull requests and reviews,** fetched once and then only what changed, so Standings have more than commits.
+
+## Consequences
+
+- The App may need the read-only "Read user profile" permission. The owner adds it if Phase 33 shows it is needed.
+- Postgres gains Profiles, stored GitHub copies, hiding, Rivals, Races, Crews and Seasons.
+- `/privacy` is rewritten to say what a Profile shows and how to hide.
+- The repository page stays, trimmed, as a secondary page.
diff --git a/docs/adr/0021-cards-are-react-components-rendered-to-svg-and-png.md b/docs/adr/0021-cards-are-react-components-rendered-to-svg-and-png.md
new file mode 100644
index 0000000..fc2e1c0
--- /dev/null
+++ b/docs/adr/0021-cards-are-react-components-rendered-to-svg-and-png.md
@@ -0,0 +1,25 @@
+# Cards are React components, rendered to SVG for READMEs and PNG for previews
+
+Each Card is written once as a React component with inline styles. The Site shows it as a component; Satori turns it into SVG for README embeds, with CSS animation added; resvg turns that SVG into PNG for link previews and downloads.
+
+## Status
+
+accepted (2026-10-04, Build Run 5 plan). Replaces the single composited repository Card.
+
+## Context
+
+The owner wants a set of Cards a person picks from, animated README embeds, and dynamic preview images like GitHub's. GitHub strips HTML, styles and scripts from READMEs and serves images through its own caching proxy, so only an image survives there. An SVG with CSS keyframes animates inside a README; a GIF is larger, blurry when scaled and has no dark mode. Designing in HTML and CSS is faster and better than writing SVG by hand.
+
+## Decision
+
+- **One component per Card,** in `packages/ui`, using only the CSS Satori supports (flexbox, no grid).
+- **SVG:** Satori renders the component; a small post-processing step adds keyframe animation (counting numbers, growing bars, a filling calendar) to marked elements. Light and dark come as two URLs, for GitHub's `` with `prefers-color-scheme`.
+- **PNG:** resvg renders the same SVG, unanimated, for Open Graph images and downloads.
+- **Fonts** are bundled files, so output is the same on every machine.
+- **Stored copies.** A Card is rendered when its data changes, kept in R2, and served with `cache-control` of six hours. A request never waits on GitHub or the Builder; a stale Card is served and refreshed in the background.
+
+## Consequences
+
+- Satori's CSS subset limits Card layouts, and some fine typography won't come out the same as on the Site.
+- Every Card has SVG and PNG snapshot tests.
+- The Builder's existing resvg dependency moves to the Site, or Card rendering moves to the Builder. Phase 36 decides by measured render time.
diff --git a/docs/adr/0022-surviving-lines-come-from-blame-per-person-on-request.md b/docs/adr/0022-surviving-lines-come-from-blame-per-person-on-request.md
new file mode 100644
index 0000000..0a3b29e
--- /dev/null
+++ b/docs/adr/0022-surviving-lines-come-from-blame-per-person-on-request.md
@@ -0,0 +1,24 @@
+# Surviving Lines come from blame, per person, on request, and are kept per head
+
+A person's Surviving Lines are the lines at a repository's head that blame attributes to them. They are computed only over the files that person changed, when someone asks, and kept for that head. Over a time budget the number is "not counted", never an estimate.
+
+## Status
+
+accepted (2026-10-04, Build Run 5 plan). Builds on ADR-0011 (identities) and ADR-0012 (line counts in a background pass); goes further than Code Age's per-file measure.
+
+## Context
+
+"41k of the 120k lines I wrote still run" is the number nobody else shows, since it needs the history itself, and it is the hardest one to inflate. Blaming every file of a large repository takes minutes (rust-lang/rust has over 100,000 files), and a reformat would take the credit for every line it touched.
+
+## Decision
+
+- **Blame at the head**, attributing each line to an Author Identity. Bulk Commits and commits in `.git-blame-ignore-revs` are passed through, so their lines go to whoever wrote them before. Generated Files and Prose Files are not counted.
+- **Per person, on request:** only files at the head that the person ever changed are blamed. The result is kept per repository, head and person. A new head means a new count, starting from the files changed since.
+- **A time budget per request,** measured in Phase 34. Beyond it the Profile says "not counted for this repository" next to the number.
+- **Survival** is Surviving Lines divided by the lines the person added in that repository (Lines Changed). It is shown only when both are known.
+
+## Consequences
+
+- The engine gains a blame pass, through gix, behind the existing `RepoSource` seam with a scripted fake for tests.
+- The Builder keeps clones of repositories that people have asked about, within a disk budget, so later requests don't clone again.
+- Code Age keeps its per-file meaning; Surviving Lines is a separate term.
From 351af3502ba81d30a1915fbe9000d68248ef3f9c Mon Sep 17 00:00:00 2001
From: Dev Talan <84081651+devchaudhary24k@users.noreply.github.com>
Date: Sun, 4 Oct 2026 22:48:45 +0530
Subject: [PATCH 02/10] feat(phase-32): build Profiles, comparisons, Cards, and
Wrapped
- Add Profiles, Standings, Surviving Lines, and repository insights
- Add Cards, Proof of Work, Versus, Rivals, Races, Crews, and Wrapped
- Add pull-request data, privacy controls, and leaderboard views
---
CONTEXT.md | 23 +-
DEPLOY.md | 6 +-
README.md | 23 +-
STATE.md | 234 +-
apps/builder/Dockerfile | 2 +-
apps/builder/package.json | 5 +-
apps/builder/src/builder.test.ts | 9 +-
apps/builder/src/config.ts | 2 +
apps/builder/src/github.ts | 23 +
apps/builder/src/job.test.ts | 33 +-
apps/builder/src/job.ts | 52 +-
apps/builder/src/main.ts | 41 +-
apps/builder/src/people.test.ts | 54 +
apps/builder/src/people.ts | 76 +
apps/builder/src/pulls.test.ts | 80 +
apps/builder/src/pulls.ts | 117 +
apps/builder/src/run.ts | 38 +-
apps/builder/src/survival.test.ts | 66 +
apps/builder/src/survival.ts | 67 +
apps/site/e2e/accounts.ts | 30 +
apps/site/e2e/boards.spec.ts | 23 +
apps/site/e2e/cards.spec.ts | 50 +
apps/site/e2e/foundation.spec.ts | 95 +
apps/site/e2e/github.ts | 11 +
apps/site/e2e/graphql.ts | 160 ++
apps/site/e2e/profile.spec.ts | 117 +
apps/site/e2e/races.spec.ts | 89 +
apps/site/e2e/site.spec.ts | 19 +-
apps/site/e2e/start.ts | 2 +
apps/site/e2e/versus.spec.ts | 42 +
apps/site/e2e/work.spec.ts | 31 +
apps/site/e2e/wrapped.spec.ts | 19 +
apps/site/package.json | 4 +
apps/site/playwright.config.ts | 1 +
apps/site/public/favicon.svg | 2 +-
apps/site/src/components/Boundary.tsx | 22 +
apps/site/src/components/CardBox.tsx | 37 +
apps/site/src/components/Connect.tsx | 32 -
apps/site/src/components/Facts.tsx | 78 +-
apps/site/src/components/Frame.tsx | 124 +-
apps/site/src/components/Membership.tsx | 65 +
apps/site/src/components/Rivals.tsx | 60 +
apps/site/src/components/Start.tsx | 116 +
apps/site/src/components/States.tsx | 26 +-
apps/site/src/components/WindowTable.tsx | 41 +
apps/site/src/functions/account.ts | 18 +-
apps/site/src/functions/cards.ts | 25 +
apps/site/src/functions/profiles.ts | 43 +
apps/site/src/functions/races.ts | 71 +
apps/site/src/functions/repos.ts | 2 +-
apps/site/src/functions/standings.ts | 39 +
apps/site/src/functions/versus.ts | 46 +
apps/site/src/functions/work.ts | 45 +
apps/site/src/lib/hydrated.ts | 12 +
apps/site/src/lib/markdown.ts | 11 +
apps/site/src/lib/queries.ts | 62 +-
apps/site/src/lib/target.ts | 11 +
apps/site/src/routeTree.gen.ts | 477 +++-
apps/site/src/routes/__root.tsx | 55 +-
.../site/src/routes/api/cards/$owner/$repo.ts | 17 -
.../src/routes/api/cards/crews/$id/$file.ts | 6 +
.../routes/api/cards/gh/$owner/$repo/$file.ts | 6 +
.../src/routes/api/cards/races/$id/$file.ts | 6 +
apps/site/src/routes/api/cards/site/$file.ts | 19 +
.../src/routes/api/cards/u/$login/$file.ts | 6 +
.../api/cards/u/$login/$owner/$repo/$file.ts | 6 +
.../api/cards/u/$login/wrapped/$year/$file.ts | 6 +
.../src/routes/api/cards/vs/$a/$b/$file.ts | 6 +
apps/site/src/routes/api/work/s/$id/$file.ts | 9 +
.../src/routes/api/work/u/$login/$file.ts | 18 +
apps/site/src/routes/crews.$id.tsx | 54 +
apps/site/src/routes/crews.index.tsx | 15 +
apps/site/src/routes/gh.$owner.$repo.tsx | 187 +-
apps/site/src/routes/index.tsx | 148 +-
apps/site/src/routes/leaderboards.tsx | 188 +-
apps/site/src/routes/me.tsx | 214 +-
apps/site/src/routes/privacy.tsx | 76 +-
apps/site/src/routes/races.$id.tsx | 60 +
apps/site/src/routes/races.index.tsx | 15 +
apps/site/src/routes/s.$id.tsx | 16 +-
apps/site/src/routes/u.$login.tsx | 237 ++
.../src/routes/u.$login_.$owner.$repo.tsx | 88 +
apps/site/src/routes/u.$login_.cards.tsx | 88 +
apps/site/src/routes/u.$login_.work.tsx | 146 ++
apps/site/src/routes/u.$login_.work_.$id.tsx | 55 +
.../src/routes/u.$login_.wrapped.$year.tsx | 111 +
apps/site/src/routes/vs.$a.$b.tsx | 129 ++
apps/site/src/routes/you.tsx | 7 +
.../__snapshots__/cards/achievement-dark.png | Bin 0 -> 42632 bytes
.../cards/achievement-dark.png.sha256 | 1 +
.../__snapshots__/cards/achievement-light.png | Bin 0 -> 43584 bytes
.../cards/achievement-light.png.sha256 | 1 +
.../__snapshots__/cards/archetype-dark.png | Bin 0 -> 47771 bytes
.../cards/archetype-dark.png.sha256 | 1 +
.../__snapshots__/cards/archetype-light.png | Bin 0 -> 48344 bytes
.../cards/archetype-light.png.sha256 | 1 +
.../__snapshots__/cards/calendar-dark.png | Bin 0 -> 49537 bytes
.../cards/calendar-dark.png.sha256 | 1 +
.../__snapshots__/cards/calendar-light.png | Bin 0 -> 49806 bytes
.../cards/calendar-light.png.sha256 | 1 +
.../__snapshots__/cards/hall-of-fame-dark.png | Bin 0 -> 58660 bytes
.../cards/hall-of-fame-dark.png.sha256 | 1 +
.../cards/hall-of-fame-light.png | Bin 0 -> 60967 bytes
.../cards/hall-of-fame-light.png.sha256 | 1 +
.../__snapshots__/cards/languages-dark.png | Bin 0 -> 33855 bytes
.../cards/languages-dark.png.sha256 | 1 +
.../__snapshots__/cards/languages-light.png | Bin 0 -> 34247 bytes
.../cards/languages-light.png.sha256 | 1 +
.../__snapshots__/cards/preview-dark.png | Bin 0 -> 96501 bytes
.../cards/preview-dark.png.sha256 | 1 +
.../__snapshots__/cards/preview-light.png | Bin 0 -> 98487 bytes
.../cards/preview-light.png.sha256 | 1 +
.../server/__snapshots__/cards/race-dark.png | Bin 0 -> 51237 bytes
.../__snapshots__/cards/race-dark.png.sha256 | 1 +
.../server/__snapshots__/cards/race-light.png | Bin 0 -> 52360 bytes
.../__snapshots__/cards/race-light.png.sha256 | 1 +
.../__snapshots__/cards/repositories-dark.png | Bin 0 -> 50769 bytes
.../cards/repositories-dark.png.sha256 | 1 +
.../cards/repositories-light.png | Bin 0 -> 52134 bytes
.../cards/repositories-light.png.sha256 | 1 +
.../__snapshots__/cards/season-dark.png | Bin 0 -> 51237 bytes
.../cards/season-dark.png.sha256 | 1 +
.../__snapshots__/cards/season-light.png | Bin 0 -> 52360 bytes
.../cards/season-light.png.sha256 | 1 +
.../__snapshots__/cards/standing-dark.png | Bin 0 -> 59547 bytes
.../cards/standing-dark.png.sha256 | 1 +
.../__snapshots__/cards/standing-light.png | Bin 0 -> 61381 bytes
.../cards/standing-light.png.sha256 | 1 +
.../__snapshots__/cards/survival-dark.png | Bin 0 -> 41280 bytes
.../cards/survival-dark.png.sha256 | 1 +
.../__snapshots__/cards/survival-light.png | Bin 0 -> 42368 bytes
.../cards/survival-light.png.sha256 | 1 +
.../__snapshots__/cards/totals-dark.png | Bin 0 -> 41739 bytes
.../cards/totals-dark.png.sha256 | 1 +
.../__snapshots__/cards/totals-light.png | Bin 0 -> 42655 bytes
.../cards/totals-light.png.sha256 | 1 +
.../__snapshots__/cards/versus-dark.png | Bin 0 -> 71627 bytes
.../cards/versus-dark.png.sha256 | 1 +
.../__snapshots__/cards/versus-light.png | Bin 0 -> 73317 bytes
.../cards/versus-light.png.sha256 | 1 +
.../cards/wrapped-calendar-dark.png | Bin 0 -> 44920 bytes
.../cards/wrapped-calendar-dark.png.sha256 | 1 +
.../cards/wrapped-calendar-light.png | Bin 0 -> 45083 bytes
.../cards/wrapped-calendar-light.png.sha256 | 1 +
.../__snapshots__/cards/wrapped-dark.png | Bin 0 -> 133788 bytes
.../cards/wrapped-dark.png.sha256 | 1 +
.../__snapshots__/cards/wrapped-light.png | Bin 0 -> 135502 bytes
.../cards/wrapped-light.png.sha256 | 1 +
apps/site/src/server/cards.test.ts | 98 +
apps/site/src/server/cards.ts | 210 ++
apps/site/src/server/engine.test.ts | 60 +
apps/site/src/server/engine.ts | 104 +
apps/site/src/server/graphql.ts | 22 +
apps/site/src/server/mcp.ts | 32 +-
apps/site/src/server/me.ts | 7 +-
apps/site/src/server/people-boards.test.ts | 41 +
apps/site/src/server/people-boards.ts | 88 +
apps/site/src/server/people.ts | 39 +
apps/site/src/server/profiles.test.ts | 217 ++
apps/site/src/server/profiles.ts | 539 +++++
apps/site/src/server/races.test.ts | 97 +
apps/site/src/server/races.ts | 207 ++
apps/site/src/server/repos.ts | 26 +-
apps/site/src/server/serve-card.ts | 28 +
apps/site/src/server/serve-work.ts | 15 +
apps/site/src/server/standings.test.ts | 102 +
apps/site/src/server/standings.ts | 137 ++
apps/site/src/server/traits.test.ts | 8 +
apps/site/src/server/traits.ts | 14 +
apps/site/src/server/versus.test.ts | 92 +
apps/site/src/server/versus.ts | 73 +
apps/site/src/server/viewer.ts | 24 +
apps/site/src/server/work-pdf.ts | 39 +
apps/site/src/server/work.test.ts | 84 +
apps/site/src/server/work.ts | 134 ++
apps/site/src/server/wrapped.ts | 11 +
apps/site/src/site.css | 94 +-
apps/site/src/start.ts | 6 +-
apps/site/vite.config.ts | 4 +-
crates/commitscape-core/src/index.rs | 6 +
crates/commitscape-index/src/blame.rs | 521 +++++
.../src/cache/blame_store.rs | 215 ++
.../commitscape-index/src/cache/line_store.rs | 4 +-
crates/commitscape-index/src/cache/mod.rs | 2 +
.../commitscape-index/src/gix_source/blame.rs | 223 ++
.../commitscape-index/src/gix_source/mod.rs | 93 +-
crates/commitscape-index/src/lib.rs | 14 +-
crates/commitscape-index/src/lines.rs | 10 +-
crates/commitscape-index/src/scripted.rs | 116 +-
crates/commitscape-index/src/similarity.rs | 203 ++
crates/commitscape-index/src/source.rs | 42 +
crates/commitscape-index/src/surviving.rs | 594 +++++
crates/commitscape-report/src/api.rs | 32 +
crates/commitscape/src/main.rs | 29 +-
crates/commitscape/src/surviving.rs | 238 ++
crates/commitscape/tests/surviving.rs | 248 ++
...eact-components-rendered-to-svg-and-png.md | 8 +
...s-come-from-blame-per-person-on-request.md | 9 +
...ad-in-two-stages-and-kept-as-two-copies.md | 27 +
docs/fixtures.md | 79 +
packages/data/src/cards.ts | 45 +
packages/data/src/github.test.ts | 14 +-
packages/data/src/github.ts | 19 +
packages/data/src/index.ts | 8 +
packages/data/src/profile.ts | 111 +
packages/data/src/races.test.ts | 24 +
packages/data/src/races.ts | 54 +
packages/data/src/standings.test.ts | 21 +
packages/data/src/standings.ts | 55 +
packages/data/src/traits.test.ts | 160 ++
packages/data/src/traits.ts | 138 ++
packages/data/src/types.ts | 6 +
packages/data/src/versus.test.ts | 28 +
packages/data/src/versus.ts | 50 +
packages/data/src/work.test.ts | 33 +
packages/data/src/work.ts | 129 ++
packages/data/src/wrapped.test.ts | 33 +
packages/data/src/wrapped.ts | 70 +
packages/server/drizzle/0001_repo_people.sql | 16 +
packages/server/drizzle/0002_profiles.sql | 13 +
packages/server/drizzle/0003_surviving.sql | 17 +
packages/server/drizzle/0004_standings.sql | 39 +
packages/server/drizzle/0005_cards.sql | 1 +
packages/server/drizzle/0006_proofs.sql | 10 +
packages/server/drizzle/0007_rivals.sql | 8 +
packages/server/drizzle/0008_oldest_line.sql | 1 +
.../server/drizzle/0009_races_and_crews.sql | 57 +
.../server/drizzle/meta/0001_snapshot.json | 1018 ++++++++
.../server/drizzle/meta/0002_snapshot.json | 1103 +++++++++
.../server/drizzle/meta/0003_snapshot.json | 1227 ++++++++++
.../server/drizzle/meta/0004_snapshot.json | 1519 ++++++++++++
.../server/drizzle/meta/0005_snapshot.json | 1513 ++++++++++++
.../server/drizzle/meta/0006_snapshot.json | 1586 +++++++++++++
.../server/drizzle/meta/0007_snapshot.json | 1639 +++++++++++++
.../server/drizzle/meta/0008_snapshot.json | 1645 +++++++++++++
.../server/drizzle/meta/0009_snapshot.json | 2042 +++++++++++++++++
packages/server/drizzle/meta/_journal.json | 63 +
packages/server/src/db/schema.ts | 195 +-
packages/server/src/queue.ts | 13 +-
packages/server/src/repos.ts | 2 +-
packages/ui/package.json | 8 +-
packages/ui/src/App.tsx | 189 +-
packages/ui/src/Palette.tsx | 21 +-
packages/ui/src/cards/Cards.tsx | 527 +++++
.../cards/__snapshots__/achievement-dark.svg | 1 +
.../cards/__snapshots__/achievement-light.svg | 1 +
.../cards/__snapshots__/archetype-dark.svg | 1 +
.../cards/__snapshots__/archetype-light.svg | 1 +
.../src/cards/__snapshots__/calendar-dark.svg | 1 +
.../cards/__snapshots__/calendar-light.svg | 1 +
.../cards/__snapshots__/hall-of-fame-dark.svg | 1 +
.../__snapshots__/hall-of-fame-light.svg | 1 +
.../cards/__snapshots__/languages-dark.svg | 1 +
.../cards/__snapshots__/languages-light.svg | 1 +
.../src/cards/__snapshots__/preview-dark.svg | 1 +
.../src/cards/__snapshots__/preview-light.svg | 1 +
.../ui/src/cards/__snapshots__/race-dark.svg | 1 +
.../ui/src/cards/__snapshots__/race-light.svg | 1 +
.../cards/__snapshots__/repositories-dark.svg | 1 +
.../__snapshots__/repositories-light.svg | 1 +
.../src/cards/__snapshots__/season-dark.svg | 1 +
.../src/cards/__snapshots__/season-light.svg | 1 +
.../src/cards/__snapshots__/standing-dark.svg | 1 +
.../cards/__snapshots__/standing-light.svg | 1 +
.../src/cards/__snapshots__/survival-dark.svg | 1 +
.../cards/__snapshots__/survival-light.svg | 1 +
.../src/cards/__snapshots__/totals-dark.svg | 1 +
.../src/cards/__snapshots__/totals-light.svg | 1 +
.../src/cards/__snapshots__/versus-dark.svg | 1 +
.../src/cards/__snapshots__/versus-light.svg | 1 +
.../__snapshots__/wrapped-calendar-dark.svg | 1 +
.../__snapshots__/wrapped-calendar-light.svg | 1 +
.../src/cards/__snapshots__/wrapped-dark.svg | 1 +
.../src/cards/__snapshots__/wrapped-light.svg | 1 +
packages/ui/src/cards/cards.test.tsx | 22 +
packages/ui/src/cards/fixture.ts | 103 +
packages/ui/src/cards/fonts.ts | 5 +
packages/ui/src/cards/fonts/LICENSE-Inter.txt | 93 +
packages/ui/src/cards/fonts/inter-400.woff | Bin 0 -> 30696 bytes
packages/ui/src/cards/fonts/inter-600.woff | Bin 0 -> 31260 bytes
packages/ui/src/cards/fonts/inter-700.woff | Bin 0 -> 31320 bytes
packages/ui/src/cards/kinds.ts | 39 +
packages/ui/src/cards/paint.test.ts | 20 +
packages/ui/src/cards/paint.ts | 52 +
packages/ui/src/cards/render.tsx | 44 +
packages/ui/src/cards/sizes.ts | 45 +
packages/ui/src/cards/tokens.ts | 54 +
packages/ui/src/cards/values.ts | 10 +
packages/ui/src/charts/Columns.tsx | 166 +-
packages/ui/src/charts/Grid.tsx | 36 +-
packages/ui/src/charts/Hours.tsx | 10 +-
packages/ui/src/charts/Lines.tsx | 16 +-
packages/ui/src/charts/columns.test.ts | 23 +
packages/ui/src/charts/common.tsx | 2 +-
packages/ui/src/charts/marks.ts | 40 +
packages/ui/src/charts/scale.ts | 19 +-
packages/ui/src/components/Face.tsx | 9 +
packages/ui/src/components/Loading.tsx | 97 +-
packages/ui/src/components/Logo.tsx | 9 +-
packages/ui/src/components/Name.tsx | 22 +-
packages/ui/src/components/login.ts | 9 +
packages/ui/src/components/skeletons.ts | 11 +
packages/ui/src/data.ts | 28 +-
packages/ui/src/format.ts | 3 +-
packages/ui/src/help.ts | 2 +-
packages/ui/src/index.css | 143 +-
packages/ui/src/index.ts | 21 +-
packages/ui/src/jump.test.ts | 4 +-
packages/ui/src/jump.ts | 3 +-
packages/ui/src/keys.ts | 4 +-
packages/ui/src/mode.ts | 12 +
packages/ui/src/profile/engine.tsx | 71 +
packages/ui/src/profile/sections.tsx | 239 ++
packages/ui/src/profile/survival.test.ts | 12 +
packages/ui/src/profile/survival.ts | 6 +
packages/ui/src/profile/traits.tsx | 57 +
packages/ui/src/route.ts | 5 +-
packages/ui/src/screens/Activity.tsx | 32 +-
packages/ui/src/screens/Commits.tsx | 25 +-
packages/ui/src/screens/Map.tsx | 14 +-
packages/ui/src/screens/Overview.tsx | 370 +--
packages/ui/src/screens/People.tsx | 43 +-
packages/ui/src/screens/Risk.tsx | 195 --
packages/ui/src/standings/Standings.tsx | 113 +
packages/ui/src/standings/places.ts | 20 +
packages/ui/src/theme.ts | 60 +-
packages/ui/src/work/WorkView.tsx | 60 +
packages/ui/src/worker.d.ts | 5 +
packages/ui/vitest.config.ts | 2 +-
pnpm-lock.yaml | 261 ++-
xtask/src/fixtures.rs | 142 ++
331 files changed, 27280 insertions(+), 1626 deletions(-)
create mode 100644 apps/builder/src/github.ts
create mode 100644 apps/builder/src/people.test.ts
create mode 100644 apps/builder/src/people.ts
create mode 100644 apps/builder/src/pulls.test.ts
create mode 100644 apps/builder/src/pulls.ts
create mode 100644 apps/builder/src/survival.test.ts
create mode 100644 apps/builder/src/survival.ts
create mode 100644 apps/site/e2e/accounts.ts
create mode 100644 apps/site/e2e/boards.spec.ts
create mode 100644 apps/site/e2e/cards.spec.ts
create mode 100644 apps/site/e2e/foundation.spec.ts
create mode 100644 apps/site/e2e/graphql.ts
create mode 100644 apps/site/e2e/profile.spec.ts
create mode 100644 apps/site/e2e/races.spec.ts
create mode 100644 apps/site/e2e/versus.spec.ts
create mode 100644 apps/site/e2e/work.spec.ts
create mode 100644 apps/site/e2e/wrapped.spec.ts
create mode 100644 apps/site/src/components/Boundary.tsx
create mode 100644 apps/site/src/components/CardBox.tsx
delete mode 100644 apps/site/src/components/Connect.tsx
create mode 100644 apps/site/src/components/Membership.tsx
create mode 100644 apps/site/src/components/Rivals.tsx
create mode 100644 apps/site/src/components/Start.tsx
create mode 100644 apps/site/src/components/WindowTable.tsx
create mode 100644 apps/site/src/functions/cards.ts
create mode 100644 apps/site/src/functions/profiles.ts
create mode 100644 apps/site/src/functions/races.ts
create mode 100644 apps/site/src/functions/standings.ts
create mode 100644 apps/site/src/functions/versus.ts
create mode 100644 apps/site/src/functions/work.ts
create mode 100644 apps/site/src/lib/hydrated.ts
create mode 100644 apps/site/src/lib/markdown.ts
create mode 100644 apps/site/src/lib/target.ts
delete mode 100644 apps/site/src/routes/api/cards/$owner/$repo.ts
create mode 100644 apps/site/src/routes/api/cards/crews/$id/$file.ts
create mode 100644 apps/site/src/routes/api/cards/gh/$owner/$repo/$file.ts
create mode 100644 apps/site/src/routes/api/cards/races/$id/$file.ts
create mode 100644 apps/site/src/routes/api/cards/site/$file.ts
create mode 100644 apps/site/src/routes/api/cards/u/$login/$file.ts
create mode 100644 apps/site/src/routes/api/cards/u/$login/$owner/$repo/$file.ts
create mode 100644 apps/site/src/routes/api/cards/u/$login/wrapped/$year/$file.ts
create mode 100644 apps/site/src/routes/api/cards/vs/$a/$b/$file.ts
create mode 100644 apps/site/src/routes/api/work/s/$id/$file.ts
create mode 100644 apps/site/src/routes/api/work/u/$login/$file.ts
create mode 100644 apps/site/src/routes/crews.$id.tsx
create mode 100644 apps/site/src/routes/crews.index.tsx
create mode 100644 apps/site/src/routes/races.$id.tsx
create mode 100644 apps/site/src/routes/races.index.tsx
create mode 100644 apps/site/src/routes/u.$login.tsx
create mode 100644 apps/site/src/routes/u.$login_.$owner.$repo.tsx
create mode 100644 apps/site/src/routes/u.$login_.cards.tsx
create mode 100644 apps/site/src/routes/u.$login_.work.tsx
create mode 100644 apps/site/src/routes/u.$login_.work_.$id.tsx
create mode 100644 apps/site/src/routes/u.$login_.wrapped.$year.tsx
create mode 100644 apps/site/src/routes/vs.$a.$b.tsx
create mode 100644 apps/site/src/routes/you.tsx
create mode 100644 apps/site/src/server/__snapshots__/cards/achievement-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/achievement-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/achievement-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/achievement-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/archetype-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/archetype-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/archetype-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/archetype-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/calendar-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/calendar-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/calendar-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/calendar-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/hall-of-fame-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/hall-of-fame-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/hall-of-fame-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/hall-of-fame-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/languages-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/languages-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/languages-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/languages-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/preview-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/preview-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/preview-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/preview-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/race-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/race-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/race-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/race-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/repositories-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/repositories-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/repositories-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/repositories-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/season-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/season-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/season-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/season-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/standing-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/standing-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/standing-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/standing-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/survival-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/survival-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/survival-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/survival-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/totals-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/totals-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/totals-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/totals-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/versus-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/versus-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/versus-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/versus-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/wrapped-calendar-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/wrapped-calendar-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/wrapped-calendar-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/wrapped-calendar-light.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/wrapped-dark.png
create mode 100644 apps/site/src/server/__snapshots__/cards/wrapped-dark.png.sha256
create mode 100644 apps/site/src/server/__snapshots__/cards/wrapped-light.png
create mode 100644 apps/site/src/server/__snapshots__/cards/wrapped-light.png.sha256
create mode 100644 apps/site/src/server/cards.test.ts
create mode 100644 apps/site/src/server/cards.ts
create mode 100644 apps/site/src/server/engine.test.ts
create mode 100644 apps/site/src/server/engine.ts
create mode 100644 apps/site/src/server/graphql.ts
create mode 100644 apps/site/src/server/people-boards.test.ts
create mode 100644 apps/site/src/server/people-boards.ts
create mode 100644 apps/site/src/server/people.ts
create mode 100644 apps/site/src/server/profiles.test.ts
create mode 100644 apps/site/src/server/profiles.ts
create mode 100644 apps/site/src/server/races.test.ts
create mode 100644 apps/site/src/server/races.ts
create mode 100644 apps/site/src/server/serve-card.ts
create mode 100644 apps/site/src/server/serve-work.ts
create mode 100644 apps/site/src/server/standings.test.ts
create mode 100644 apps/site/src/server/standings.ts
create mode 100644 apps/site/src/server/traits.test.ts
create mode 100644 apps/site/src/server/traits.ts
create mode 100644 apps/site/src/server/versus.test.ts
create mode 100644 apps/site/src/server/versus.ts
create mode 100644 apps/site/src/server/work-pdf.ts
create mode 100644 apps/site/src/server/work.test.ts
create mode 100644 apps/site/src/server/work.ts
create mode 100644 apps/site/src/server/wrapped.ts
create mode 100644 crates/commitscape-index/src/blame.rs
create mode 100644 crates/commitscape-index/src/cache/blame_store.rs
create mode 100644 crates/commitscape-index/src/gix_source/blame.rs
create mode 100644 crates/commitscape-index/src/similarity.rs
create mode 100644 crates/commitscape-index/src/surviving.rs
create mode 100644 crates/commitscape/src/surviving.rs
create mode 100644 crates/commitscape/tests/surviving.rs
create mode 100644 docs/adr/0023-a-profile-is-read-in-two-stages-and-kept-as-two-copies.md
create mode 100644 packages/data/src/cards.ts
create mode 100644 packages/data/src/profile.ts
create mode 100644 packages/data/src/races.test.ts
create mode 100644 packages/data/src/races.ts
create mode 100644 packages/data/src/standings.test.ts
create mode 100644 packages/data/src/standings.ts
create mode 100644 packages/data/src/traits.test.ts
create mode 100644 packages/data/src/traits.ts
create mode 100644 packages/data/src/versus.test.ts
create mode 100644 packages/data/src/versus.ts
create mode 100644 packages/data/src/work.test.ts
create mode 100644 packages/data/src/work.ts
create mode 100644 packages/data/src/wrapped.test.ts
create mode 100644 packages/data/src/wrapped.ts
create mode 100644 packages/server/drizzle/0001_repo_people.sql
create mode 100644 packages/server/drizzle/0002_profiles.sql
create mode 100644 packages/server/drizzle/0003_surviving.sql
create mode 100644 packages/server/drizzle/0004_standings.sql
create mode 100644 packages/server/drizzle/0005_cards.sql
create mode 100644 packages/server/drizzle/0006_proofs.sql
create mode 100644 packages/server/drizzle/0007_rivals.sql
create mode 100644 packages/server/drizzle/0008_oldest_line.sql
create mode 100644 packages/server/drizzle/0009_races_and_crews.sql
create mode 100644 packages/server/drizzle/meta/0001_snapshot.json
create mode 100644 packages/server/drizzle/meta/0002_snapshot.json
create mode 100644 packages/server/drizzle/meta/0003_snapshot.json
create mode 100644 packages/server/drizzle/meta/0004_snapshot.json
create mode 100644 packages/server/drizzle/meta/0005_snapshot.json
create mode 100644 packages/server/drizzle/meta/0006_snapshot.json
create mode 100644 packages/server/drizzle/meta/0007_snapshot.json
create mode 100644 packages/server/drizzle/meta/0008_snapshot.json
create mode 100644 packages/server/drizzle/meta/0009_snapshot.json
create mode 100644 packages/ui/src/cards/Cards.tsx
create mode 100644 packages/ui/src/cards/__snapshots__/achievement-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/achievement-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/archetype-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/archetype-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/calendar-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/calendar-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/hall-of-fame-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/hall-of-fame-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/languages-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/languages-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/preview-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/preview-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/race-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/race-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/repositories-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/repositories-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/season-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/season-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/standing-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/standing-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/survival-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/survival-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/totals-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/totals-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/versus-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/versus-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/wrapped-calendar-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/wrapped-calendar-light.svg
create mode 100644 packages/ui/src/cards/__snapshots__/wrapped-dark.svg
create mode 100644 packages/ui/src/cards/__snapshots__/wrapped-light.svg
create mode 100644 packages/ui/src/cards/cards.test.tsx
create mode 100644 packages/ui/src/cards/fixture.ts
create mode 100644 packages/ui/src/cards/fonts.ts
create mode 100644 packages/ui/src/cards/fonts/LICENSE-Inter.txt
create mode 100644 packages/ui/src/cards/fonts/inter-400.woff
create mode 100644 packages/ui/src/cards/fonts/inter-600.woff
create mode 100644 packages/ui/src/cards/fonts/inter-700.woff
create mode 100644 packages/ui/src/cards/kinds.ts
create mode 100644 packages/ui/src/cards/paint.test.ts
create mode 100644 packages/ui/src/cards/paint.ts
create mode 100644 packages/ui/src/cards/render.tsx
create mode 100644 packages/ui/src/cards/sizes.ts
create mode 100644 packages/ui/src/cards/tokens.ts
create mode 100644 packages/ui/src/cards/values.ts
create mode 100644 packages/ui/src/charts/columns.test.ts
create mode 100644 packages/ui/src/charts/marks.ts
create mode 100644 packages/ui/src/components/Face.tsx
create mode 100644 packages/ui/src/components/login.ts
create mode 100644 packages/ui/src/components/skeletons.ts
create mode 100644 packages/ui/src/mode.ts
create mode 100644 packages/ui/src/profile/engine.tsx
create mode 100644 packages/ui/src/profile/sections.tsx
create mode 100644 packages/ui/src/profile/survival.test.ts
create mode 100644 packages/ui/src/profile/survival.ts
create mode 100644 packages/ui/src/profile/traits.tsx
delete mode 100644 packages/ui/src/screens/Risk.tsx
create mode 100644 packages/ui/src/standings/Standings.tsx
create mode 100644 packages/ui/src/standings/places.ts
create mode 100644 packages/ui/src/work/WorkView.tsx
diff --git a/CONTEXT.md b/CONTEXT.md
index 88e91a7..9f17239 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -187,11 +187,11 @@ Said of a Profile whose person chose to stay out of comparisons: it appears in n
_Avoid_: private profile, opted out.
**Surviving Lines**:
-The lines at a repository's head that blame attributes to a person, passing through Bulk Commits and ignored revisions, leaving out Generated Files and Prose Files (ADR-0022). Counted per person on request; "not counted" when over its time budget.
+The lines at a repository's head that blame attributes to a person, passing through Bulk Commits and ignored revisions, leaving out Generated Files, Prose Files and files over a megabyte (ADR-0022). Passing through gives a changed line to the commit before only when the two are the same once whitespace is removed; any other line a reformat changed stays with the reformat. Counted per person on request; "not counted" when over its time budget, never an estimate.
_Avoid_: lines owned, code alive, impact.
**Survival**:
-A person's Surviving Lines as a share of the lines they added in that repository. Shown only when both are known.
+A person's Surviving Lines as a share of the lines they added in that repository (their Lines Changed). Shown only when both are known and it is at most 100%: an import left out of Lines Changed as a Bulk Commit can keep more lines than were counted as added.
_Avoid_: retention, survival rate, code quality.
**Standing**:
@@ -211,11 +211,26 @@ A person someone chose to measure themselves against, whose gap shows on their o
_Avoid_: friend, follow.
**Archetype**:
-A label for how a person works, from a written rule over their numbers, such as Reviewer (more reviews than pull requests) or Janitor (removes more lines than they add). Its rule is shown with it.
+A label for how a person works, from a written rule over their numbers, shown with its rule. Nobody under 50 contributions has one. The rules are tried in this order, and the first that fits is theirs (the others show as "also"):
+- **Reviewer**: gave more reviews than they opened pull requests, and at least 20 reviews.
+- **Janitor**: removed more lines than they added in their merged pull requests, and at least 1,000 lines.
+- **Firefighter**: at least half of their merged pull requests, and 10 or more, are fixes: titled fix, hotfix or bugfix, or a conventional fix:.
+- **Night Owl**: at least 40% of their newest 100 commits, 30 or more read, were made between 22:00 and 04:59 on the commit's own clock.
+- **Polyglot**: committed in five or more languages, each at least 5% of their commits, by the main language of each repository.
+- **Weekend Warrior**: at least 40% of their active days in the last year fell on a Saturday or Sunday, with 30 or more active days.
+- **Marathoner**: a streak of 30 days or more in a row with a contribution.
+- **Builder**: merged 25 or more pull requests, adding at least twice the lines they removed.
_Avoid_: personality, type, persona.
**Achievement**:
-A milestone a person reached, from a written rule, such as a first merged pull request in a repository with 10,000 stars. Each has a Card.
+A milestone a person reached, from a written rule, shown with the day it was reached when that is known. Each has a Card. The set:
+- **Merged into a 10k-star repository**: a pull request of theirs merged into a repository with 10,000 stars or more, as it has now.
+- **100** and **1,000 pull requests merged**.
+- **100** and **1,000 reviews** of others' pull requests.
+- **Removed 10k lines in one pull request**: a merged pull request of theirs that removed 10,000 lines or more.
+- **A 30-day** and **a 100-day streak** of contributions.
+- **10,000 lines still running**: Surviving Lines at the heads of the repositories commitscape has read.
+- **A line that has survived five years**: a line of theirs still at a repository's head, from a commit authored five years ago or more.
_Avoid_: badge, trophy.
**Season**:
diff --git a/DEPLOY.md b/DEPLOY.md
index 7db9039..c49af65 100644
--- a/DEPLOY.md
+++ b/DEPLOY.md
@@ -97,7 +97,7 @@ S3_ENDPOINT=https://.r2.cloudflarestorage.com
S3_BUCKET=commitscape-reports
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
-GITHUB_TOKEN=... # a token with no scopes: GitHub's search limits for the Leaderboards
+GITHUB_TOKEN=... # a token with no scopes: GitHub's search limits for the Leaderboards, people's logins, and each repository's pull requests
GITHUB_APP_ID=... # for private repositories
GITHUB_APP_PRIVATE_KEY="..."
```
@@ -111,6 +111,8 @@ Optional limits, with their defaults:
| `MAX_REPOSITORY_MB` | 3000 | Larger repositories are refused |
| `TIME_LIMIT_SECONDS` | 900 | A Build stops after this long |
| `DISK_BUDGET_GB` | 20 | Past this, the least recently built clones in `/work` are deleted |
+| `SURVIVING_BUDGET_SECONDS` | 60 | How long one count of a person's Surviving Lines may take before it says "not counted" |
+| `PULLS_TIME_LIMIT_SECONDS` | 1800 | How long one read of a repository's pull requests may take; facebook/react's first read took 24 minutes, so a busy repository's first read may need longer, once |
| `SEED_LANGUAGES`, `SEED_PER_LANGUAGE`, `SEED_BUDGET` | 9 languages, 10, 50 | The nightly Leaderboard Builds |
Deploy it. It applies the database migrations, then logs `builder working`.
@@ -141,7 +143,7 @@ S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
BETTER_AUTH_URL=https://example.com
BETTER_AUTH_SECRET=... # openssl rand -hex 32
-GITHUB_TOKEN=... # the same token: GitHub's API limits for lookups
+GITHUB_TOKEN=... # the same token, required: Profiles, Proof of Work, Races and Crews read GitHub's GraphQL API, which needs a token; signed-in people use their own
GITHUB_APP_ID=...
GITHUB_APP_SLUG=...
GITHUB_APP_CLIENT_ID=...
diff --git a/README.md b/README.md
index 285e04c..b9334de 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
commitscape
-
Reads a git repository's history and shows what changes what you do next.
+
What you have built, how you stand next to the people you work with, and cards to share it.
@@ -16,11 +16,12 @@
-commitscape reads a git repository and answers the questions its history
-can answer: who knows which part of the code, which files are risky to
-change, what you probably forgot to change, and how the project got here.
-It reads the whole history of a large repository in seconds, on your
-machine, from git's own data.
+commitscape shows a developer what they have built: pull requests merged,
+reviews given, and the lines of theirs that still run, counted from each
+repository's own history with blame. It shows how they stand next to the
+people they work with, view by view and never by one score, and turns it
+into Cards for a README, a self-review or a post. On your machine it reads
+the whole history of a large repository in seconds, from git's own data.

@@ -31,9 +32,12 @@ There are three ways to use it:
2. **Shared from a terminal.** `commitscape share` builds the Report on
your machine, encrypts it, uploads it and prints a link. The link opens
in any browser for 4 hours, and the Site cannot read what it stores.
-3. **On the Site.** Paste any public GitHub link to see its Report, sign in
- with GitHub to open your private repositories, or browse the
- Leaderboards.
+3. **On the Site.** Type any GitHub username for their Profile: totals,
+ Surviving Lines, Archetype and Achievements, where their work is, Cards
+ to embed, Proof of Work for a period, Versus, Rivals, Races, Crews and
+ Wrapped. Paste a repository link for its Report and Standings, sign in
+ with GitHub to see your own private work, or browse the Leaderboards.
+ Anyone can stay out of comparisons.
The project is in early development. The CLI and the Site work end to end;
the first release is next.
@@ -79,6 +83,7 @@ once it runs.
| `commitscape health ` | Whether a GitHub project is alive and whether it depends on one person |
| `commitscape wrapped [folder]` | Your year across every repository under a folder, as a card |
| `commitscape card [path]` | The repository's story as an SVG card, like the one above |
+| `commitscape surviving [path] --person ` | A person's Surviving Lines: the lines at the head that blame gives them, reformats passed through |
| `commitscape share` | An encrypted link to this repository's Report, for any browser |
| `commitscape report [path]` | The Report as gzipped JSON, as the Site stores it |
| `commitscape github [path]` | Fetch pull requests, issues and releases through the `gh` CLI |
diff --git a/STATE.md b/STATE.md
index 9c35afc..694c0b5 100644
--- a/STATE.md
+++ b/STATE.md
@@ -3,7 +3,7 @@
Running log for Build Run 1 (Phases 0 to 7). Written so a fresh session with
no context can read this plus `docs/adr/` and continue without asking anything.
-**Current position:** Build Run 5 (Phases 32 to 42) is planned and not started: its brief is `IDEA.md`, with ADR-0020 to ADR-0022, written 2026-10-04 after the owner's review of the live Site. Build Run 4 (Phases 23 to 31) is done, all of it uncommitted in the working tree: its brief is `IDEA.md`, with ADR-0013 to ADR-0019. Every phase's findings and the files it created, changed or deleted are below. Build Run 3 is done (Phases 13 to 22). The repository is on GitHub (`pixelactstudio/commitscape`) and CI is green on Linux, macOS and Windows for what is committed. Nothing is published or deployed: the name may still change.
+**Current position:** Build Run 5 (Phases 32 to 42) is done, uncommitted in the working tree: its brief is `IDEA.md`, with ADR-0020 to ADR-0022 and ADR-0023, written during the run. The table under "Build Run 5" gives each phase's gate and result, each phase's findings are under "Phase N findings", and "Where to pick up" (at the end) lists what is left for the owner. Phase 36's README embed and Phase 40's three real accounts need the owner; everything else met its gate. Build Run 4 (Phases 23 to 31) is committed. Since Build Run 4 the Site moved from Cloudflare to TanStack Start on Node with Postgres and R2 (`CLAUDE.md`, `DEPLOY.md`); older findings below that mention D1, Workers or `wrangler` describe that earlier Site.
Build Run 1 (Phases 0 to 7) built a correct, fast tool. Build Run 2 (Phases 8
to 12) made it fun and visual. On 2026-09-24 the owner used it on their own
repositories and reviewed it. The review and the decisions that followed are
@@ -288,6 +288,24 @@ gate for each phase.
| 21 | `wrapped` and the README card Action | **DONE**: the owner's 2026 across `~/code` in `target/preview/wrapped/`; hand-worked test for the year; the card Action simulated against a local remote |
| 22 | Distribution (npm, Homebrew, Nix) and launch material | **DONE, but not published**: `npx commitscape` ran on clean Debian and Alpine containers from the packed tarballs; the flake builds; nothing is on npm yet (no remote, no token) |
+### Build Run 5
+
+The brief is `IDEA.md` (written 2026-10-04): the Site turns from a repository's Report to a person's Profile, with Standings, Cards and the fun side (ADR-0020 to ADR-0022). The agent doing the work never commits, pushes, publishes or deploys; the owner commits.
+
+| Phase | What | Status |
+|---|---|---|
+| 32 | Foundation: server rendering and streaming, no theme flash, the logo, the chart drawn once, releases thinned, the repository page trimmed with avatars and search | **DONE**: 32 Playwright tests; layout shift 0.0000 on all ten pages measured (People 0.0589 before the fix in finding 6); no server function asked again after hydration on any of them; screenshots in both themes in `target/preview/site/phase32-*` |
+| 33 | The Profile, first speed: `/u/` from GitHub's API, stored copies, the viewer's token, the per-project breakdown, the new landing page | **DONE**: 7 vitest tests against GitHub answers written by hand, 4 Playwright tests (Profile, preview, failures, landing); the owner's, gaearon's and torvalds' Profiles screenshotted in both themes (`target/preview/site/phase33-*`); GitHub requests per cold view 18 to 104, per warm view 0 (finding 4); ADR-0023 |
+| 34 | The Profile, second speed: Surviving Lines in the engine, per person and head; the Builder counts on request; Lines Changed per person; sections stream in | **DONE**: hand-worked `survival` fixture (Surviving Lines, Bulk Commits, ignored revisions) through gix and through the scripted fake; measured on ripgrep, facebook/react and rust-lang/rust (finding 3); over budget is "not counted" in the engine, the Builder and the page (tests); 265 Rust tests, clippy clean; 21 Builder, Site and UI tests added; the pipeline end to end in Playwright |
+| 35 | You in this repository: `/u///`, Standings, the Builder keeping pull requests and reviews, privacy and hiding, `/privacy`, MCP tools | **DONE**: access tests (a private repository's Standings refused without access, refused when GitHub's id differs, shown with access; a hidden person absent from Standings, Profile, engine numbers, MCP and Cards, still seeing their own); facebook/react's pull requests read in 1,414.6 s the first time (20,362 pull requests, 408 pages) and 1.5 s after; 48 Playwright tests |
+| 36 | Cards: the pipeline, the set, animation, light and dark, the gallery, embeds with stored copies, a preview image on every page | **DONE, but the README embed needs the owner**: 16 SVG and 16 PNG snapshots; each Card drawn as SVG and PNG under 200 ms (39 to 135 ms warm in the dev server; tested); stored copies, stale-while-drawing and "reading" Cards tested; every page's preview image checked in Playwright. The embed in a real README on GitHub is in "Left for the owner" |
+| 37 | Proof of Work: `/u//work`, filters, share link, Markdown and PDF | **DONE**: the owner's September 2026 checked against GitHub with the `gh` CLI (finding 3); private items absent from a public page and from a shared link unless chosen (vitest); 4 vitest and 2 Playwright tests, Markdown and PDF downloads among them |
+| 38 | Versus and Rivals | **DONE**: fixture Profiles give the winners worked out by hand (pure function and through stored Profiles); a hidden Profile refused for Versus, its Card and its Profile (vitest and Playwright); 6 vitest and 2 Playwright tests; BurntSushi versus gaearon screenshotted in both themes |
+| 39 | Archetypes and Achievements, each rule in `CONTEXT.md` and on the page | **DONE**: every rule tested at its edges on fixtures (17 vitest tests, a just-below and an at-the-line case for each threshold); a test keeps CONTEXT.md's wording equal to the code's; the oldest surviving line added to the engine with hand-worked fixture values; 265 Rust tests, clippy clean, 53 Playwright tests |
+| 40 | Seasons, Races and Crews: invitations, acceptance, live Standings, recap and finish Cards | **DONE with stand-in accounts; real accounts need the owner**: end to end in Playwright with three signed-in accounts (alice, bob, carol) against the fake GitHub: a Race joined only by those who accept, live Standings and its Card; a Crew where leaving removes the person at once (checked in the page and the database); a hidden person never in it and never invitable; 4 vitest tests of the rules; 55 Playwright tests |
+| 41 | People on the Leaderboards: top contributors per Season, per repository, with a time filter | **DONE**: three people boards (pull requests merged, pull requests reviewed, Surviving Lines) over the seed repositories or one of them, for This Season, Last Season, 90 days and all time; hidden people and `[bot]` accounts absent (2 vitest tests, 1 Playwright test); built from a real local seed night of 6 repositories (20,137 pull requests read) and screenshotted in both themes; the page answers in 80 to 100 ms |
+| 42 | Wrapped 2026: a person's year across GitHub, as a page and a set of Cards | **DONE**: `wrappedOf` tested on a year worked out by hand; the owner's 2026 checked against GitHub by hand, every number equal; two Cards with snapshots; 1 Playwright test; screenshots in both themes |
+
### Build Run 4
The brief is `IDEA.md` (written 2026-09-25), with ADR-0013 to ADR-0019:
@@ -2296,6 +2314,162 @@ Files created, changed or deleted in Phase 31:
- Deleted: none (a stray `apps/builder/health`, written by a test in
Phase 30 and never tracked, was removed).
+## Phase 32 findings
+
+1. **The grey-then-blue flash was Astryx injecting the theme in the browser.** `Theme` writes a theme's CSS in `useInsertionEffect` unless the theme is marked built, so the server's page had the neutral theme's accent until hydration. `themeCss()` (`packages/ui/src/theme.ts`) now generates the same CSS with Astryx's own `generateThemeCSS`, in the same layers, and the root route puts it in a `
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/achievement-light.svg b/packages/ui/src/cards/__snapshots__/achievement-light.svg
new file mode 100644
index 0000000..634a954
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/achievement-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/archetype-dark.svg b/packages/ui/src/cards/__snapshots__/archetype-dark.svg
new file mode 100644
index 0000000..3bd44b2
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/archetype-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/archetype-light.svg b/packages/ui/src/cards/__snapshots__/archetype-light.svg
new file mode 100644
index 0000000..6a6939a
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/archetype-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/calendar-dark.svg b/packages/ui/src/cards/__snapshots__/calendar-dark.svg
new file mode 100644
index 0000000..c36354c
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/calendar-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/calendar-light.svg b/packages/ui/src/cards/__snapshots__/calendar-light.svg
new file mode 100644
index 0000000..e5d943c
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/calendar-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/hall-of-fame-dark.svg b/packages/ui/src/cards/__snapshots__/hall-of-fame-dark.svg
new file mode 100644
index 0000000..4d31f38
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/hall-of-fame-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/hall-of-fame-light.svg b/packages/ui/src/cards/__snapshots__/hall-of-fame-light.svg
new file mode 100644
index 0000000..83ed934
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/hall-of-fame-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/languages-dark.svg b/packages/ui/src/cards/__snapshots__/languages-dark.svg
new file mode 100644
index 0000000..5f566b6
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/languages-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/languages-light.svg b/packages/ui/src/cards/__snapshots__/languages-light.svg
new file mode 100644
index 0000000..6e36474
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/languages-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/preview-dark.svg b/packages/ui/src/cards/__snapshots__/preview-dark.svg
new file mode 100644
index 0000000..0b7d6ca
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/preview-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/preview-light.svg b/packages/ui/src/cards/__snapshots__/preview-light.svg
new file mode 100644
index 0000000..ed8ce82
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/preview-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/race-dark.svg b/packages/ui/src/cards/__snapshots__/race-dark.svg
new file mode 100644
index 0000000..a220c97
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/race-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/race-light.svg b/packages/ui/src/cards/__snapshots__/race-light.svg
new file mode 100644
index 0000000..bebc053
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/race-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/repositories-dark.svg b/packages/ui/src/cards/__snapshots__/repositories-dark.svg
new file mode 100644
index 0000000..8a9a173
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/repositories-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/repositories-light.svg b/packages/ui/src/cards/__snapshots__/repositories-light.svg
new file mode 100644
index 0000000..5facf63
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/repositories-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/season-dark.svg b/packages/ui/src/cards/__snapshots__/season-dark.svg
new file mode 100644
index 0000000..a220c97
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/season-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/season-light.svg b/packages/ui/src/cards/__snapshots__/season-light.svg
new file mode 100644
index 0000000..bebc053
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/season-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/standing-dark.svg b/packages/ui/src/cards/__snapshots__/standing-dark.svg
new file mode 100644
index 0000000..47f1ed3
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/standing-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/standing-light.svg b/packages/ui/src/cards/__snapshots__/standing-light.svg
new file mode 100644
index 0000000..a781d59
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/standing-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/survival-dark.svg b/packages/ui/src/cards/__snapshots__/survival-dark.svg
new file mode 100644
index 0000000..6b13e22
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/survival-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/survival-light.svg b/packages/ui/src/cards/__snapshots__/survival-light.svg
new file mode 100644
index 0000000..d7db9de
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/survival-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/totals-dark.svg b/packages/ui/src/cards/__snapshots__/totals-dark.svg
new file mode 100644
index 0000000..b6e795d
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/totals-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/totals-light.svg b/packages/ui/src/cards/__snapshots__/totals-light.svg
new file mode 100644
index 0000000..06ac2f3
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/totals-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/versus-dark.svg b/packages/ui/src/cards/__snapshots__/versus-dark.svg
new file mode 100644
index 0000000..2964275
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/versus-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/versus-light.svg b/packages/ui/src/cards/__snapshots__/versus-light.svg
new file mode 100644
index 0000000..17c36f9
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/versus-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/wrapped-calendar-dark.svg b/packages/ui/src/cards/__snapshots__/wrapped-calendar-dark.svg
new file mode 100644
index 0000000..8cb7168
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/wrapped-calendar-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/wrapped-calendar-light.svg b/packages/ui/src/cards/__snapshots__/wrapped-calendar-light.svg
new file mode 100644
index 0000000..97cbd18
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/wrapped-calendar-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/wrapped-dark.svg b/packages/ui/src/cards/__snapshots__/wrapped-dark.svg
new file mode 100644
index 0000000..42f7db8
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/wrapped-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/__snapshots__/wrapped-light.svg b/packages/ui/src/cards/__snapshots__/wrapped-light.svg
new file mode 100644
index 0000000..7670c98
--- /dev/null
+++ b/packages/ui/src/cards/__snapshots__/wrapped-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ui/src/cards/cards.test.tsx b/packages/ui/src/cards/cards.test.tsx
new file mode 100644
index 0000000..87fd1ec
--- /dev/null
+++ b/packages/ui/src/cards/cards.test.tsx
@@ -0,0 +1,22 @@
+import { describe, expect, test } from "vitest";
+import { CARDS } from "./kinds";
+import { ACHIEVEMENT, ARCHETYPE, HALL, PROFILE, STANDING, VERSUS, WINDOW, WRAPPED } from "./fixture";
+import { renderCard } from "./render";
+
+const DATA = { totals: PROFILE, survival: PROFILE, repositories: PROFILE, calendar: PROFILE, languages: PROFILE, preview: PROFILE, standing: STANDING, "hall-of-fame": HALL, versus: VERSUS, archetype: ARCHETYPE, achievement: ACHIEVEMENT, race: WINDOW, season: WINDOW, wrapped: WRAPPED, "wrapped-calendar": WRAPPED } as const;
+
+describe("every Card", () => {
+ for (const kind of Object.keys(CARDS) as (keyof typeof CARDS)[]) {
+ for (const theme of ["light", "dark"] as const) {
+ test(`${kind} in ${theme}`, async () => {
+ const spec = CARDS[kind] as (typeof CARDS)["totals"];
+ const out = await renderCard(spec, DATA[kind] as never, theme, { images: {}, site: "commitscape.example" });
+ expect(out.animated.startsWith(` ((i * 7) % 11 === 0 ? 0 : (i * 13) % 9));
+
+export const PROFILE: ProfileCardData = {
+ identity: { login: "alice", name: "Alice Example" },
+ totals: { prsOpened: 640, prsMerged: 597, prsClosed: 20, prsOpen: 23, reviews: 212, commits: 4883, issues: 41, hidden: 0, linesAdded: 182_000, linesRemoved: 66_300, hoursToMerge: 2.5, activeDays: 407, longestStreak: 33, currentStreak: 3, contributions: 5384 },
+ repositories: [
+ { owner: "acme", name: "rocket", private: false, stars: 1200, language: "TypeScript", colour: "#3178c6", commits: 271, prsOpened: 30, prsMerged: 28, reviews: 1, linesAdded: 1, linesRemoved: 1, first: null, last: null },
+ { owner: "acme", name: "engine", private: false, stars: 3, language: "Rust", colour: "#dea584", commits: 158, prsOpened: 11, prsMerged: 11, reviews: 1, linesAdded: 1, linesRemoved: 1, first: null, last: null },
+ { owner: "alice", name: "dots", private: false, stars: 0, language: "Shell", colour: "#89e051", commits: 81, prsOpened: 3, prsMerged: 3, reviews: 0, linesAdded: 1, linesRemoved: 1, first: null, last: null },
+ ],
+ years: [
+ { year: 2024, commits: 300, prs: 10, reviews: 2, issues: 0, hidden: 0, languages: [{ name: "TypeScript", colour: null, commits: 300 }] },
+ { year: 2025, commits: 900, prs: 80, reviews: 20, issues: 4, hidden: 0, languages: [{ name: "TypeScript", colour: null, commits: 800 }, { name: "Rust", colour: null, commits: 100 }] },
+ { year: 2026, commits: 3683, prs: 550, reviews: 190, issues: 37, hidden: 0, languages: [{ name: "TypeScript", colour: null, commits: 2600 }, { name: "Rust", colour: null, commits: 700 }, { name: "Nix", colour: null, commits: 300 }, { name: "Shell", colour: null, commits: 83 }] },
+ ],
+ calendar: { firstDay: 20_000, days },
+ engine: { surviving: 41_200, added: 120_400, repositories: 3 },
+ at: 1_790_000_000,
+};
+
+export const STANDING: StandingCardData = {
+ identity: { login: "alice", name: "Alice Example" },
+ repo: { owner: "facebook", name: "react" },
+ places: [
+ { view: "surviving", place: 4, of: 120, value: 22_213 },
+ { view: "prsMerged", place: 12, of: 1_890, value: 340 },
+ { view: "reviews", place: 7, of: 640, value: 910 },
+ { view: "commits", place: 12, of: 1_950, value: 1_200 },
+ ],
+ people: 1_950,
+ top: "Top 1% of facebook/react's 1,950 contributors by commits",
+ numbers: { surviving: 22_213, prsMerged: 340, reviews: 910, commits: 1_200 },
+};
+
+export const HALL: HallOfFameData = {
+ repo: { owner: "BurntSushi", name: "ripgrep", stars: 68_826 },
+ people: [
+ { login: "BurntSushi", name: "Andrew Gallant", surviving: 61_148, prsMerged: 106, commits: 1_551 },
+ { login: "dana", name: "dana", surviving: 893, prsMerged: 33, commits: 56 },
+ { login: null, name: "Igor Gnatenko", surviving: null, prsMerged: null, commits: 10 },
+ ],
+ total: 478,
+};
+
+export const VERSUS: VersusCardData = {
+ a: { identity: { login: "alice", name: "Alice Example" } },
+ b: { identity: { login: "bob", name: "Bob Example" } },
+ rows: [
+ { view: "surviving", label: "Lines that still run", a: 41_200, b: 12_000, winner: "a" },
+ { view: "prsMerged", label: "Pull requests merged", a: 597, b: 811, winner: "b" },
+ { view: "reviews", label: "Reviews given", a: 212, b: 212, winner: "tie" },
+ { view: "commits", label: "Commits", a: 4_883, b: 2_100, winner: "a" },
+ { view: "linesAdded", label: "Lines added, merged", a: 182_000, b: null, winner: null },
+ { view: "activeDays", label: "Active days", a: 407, b: 512, winner: "b" },
+ { view: "longestStreak", label: "Longest streak", a: 33, b: 21, winner: "a" },
+ { view: "hoursToMerge", label: "Time to merge", a: 2.5, b: 0.4, winner: "b" },
+ ],
+};
+
+export const ARCHETYPE: ArchetypeCardData = {
+ identity: { login: "alice", name: "Alice Example" },
+ archetype: { id: "reviewer", title: "Reviewer", rule: "Gave more reviews than they opened pull requests, and at least 20 reviews." },
+ also: ["Marathoner", "Builder"],
+};
+
+export const ACHIEVEMENT: AchievementCardData = {
+ identity: { login: "alice", name: "Alice Example" },
+ achievement: { id: "star-10k", title: "Merged into a 10k-star repository", rule: "A pull request of theirs merged into a repository with 10,000 stars or more, as it has now.", earned: true, at: "2024-03-02", detail: "facebook/react#28411" },
+};
+
+export const WINDOW: WindowCardData = {
+ title: "The October sprint",
+ subtitle: "A Race from 2026-10-01 to 2026-10-31, finished",
+ rows: [
+ { login: "alice", name: "Alice Example", prsMerged: 12, reviews: 30, commits: 140, contributions: 190 },
+ { login: "bob", name: "Bob Example", prsMerged: 12, reviews: 8, commits: 210, contributions: 230 },
+ { login: "carol", name: null, prsMerged: 3, reviews: 41, commits: 20, contributions: 70 },
+ ],
+};
+
+export const WRAPPED: WrappedCardData = {
+ login: "alice",
+ name: "Alice Example",
+ year: 2026,
+ contributions: 4_917,
+ commits: 3_683,
+ prsOpened: 600,
+ prsMerged: 581,
+ reviews: 190,
+ issues: 37,
+ linesAdded: 1_400_000,
+ linesRemoved: 520_000,
+ activeDays: 263,
+ longestStreak: 33,
+ busiest: { day: "2026-09-19", contributions: 161 },
+ languages: [{ name: "TypeScript", commits: 2600 }],
+ repositories: [{ repo: "acme/rocket", commits: 900, private: false }],
+ months: [100, 200, 300, 400, 500, 600, 700, 800, 900, 417, 0, 0],
+ calendar: { firstDay: Date.UTC(2026, 0, 1) / 86_400_000, days: Array.from({ length: 365 }, (_, i) => (i > 276 ? 0 : (i * 7) % 13)) },
+ complete: true,
+};
diff --git a/packages/ui/src/cards/fonts.ts b/packages/ui/src/cards/fonts.ts
new file mode 100644
index 0000000..beb102f
--- /dev/null
+++ b/packages/ui/src/cards/fonts.ts
@@ -0,0 +1,5 @@
+import inter400 from "./fonts/inter-400.woff?inline";
+import inter600 from "./fonts/inter-600.woff?inline";
+import inter700 from "./fonts/inter-700.woff?inline";
+
+export const INTER = { 400: inter400, 600: inter600, 700: inter700 } as const;
diff --git a/packages/ui/src/cards/fonts/LICENSE-Inter.txt b/packages/ui/src/cards/fonts/LICENSE-Inter.txt
new file mode 100644
index 0000000..40589da
--- /dev/null
+++ b/packages/ui/src/cards/fonts/LICENSE-Inter.txt
@@ -0,0 +1,93 @@
+Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) Inter-Italic[opsz,wght].ttf: Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/packages/ui/src/cards/fonts/inter-400.woff b/packages/ui/src/cards/fonts/inter-400.woff
new file mode 100644
index 0000000000000000000000000000000000000000..2f21ed484182ce5eeaf53879ea2bd2460ee06324
GIT binary patch
literal 30696
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
literal 0
HcmV?d00001
diff --git a/packages/ui/src/cards/fonts/inter-600.woff b/packages/ui/src/cards/fonts/inter-600.woff
new file mode 100644
index 0000000000000000000000000000000000000000..323fa679d99c6d019afca278b5b23fa77a1c29bd
GIT binary patch
literal 31260
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