Skip to content
128 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971
2import type { Contributor, WeekCommits } from "@g1t/contracts";
3
4import type { Route } from "./+types/contributors";
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)5import { CommitAvatar, CommitName } from "../../components/commit-person";
6import { EmptyState, TimeAgo } from "../../components/ui";
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb977import { Hint } from "../../components/ui/hint";
8import { requireRepo } from "../../lib/access.server";
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)9import { alignWeeks, count, peak } from "../../lib/about";
10import { contributorPerson } from "../../lib/commit-people";
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9711import { page } from "../../lib/meta";
12import { repos } from "../../lib/services.server";
13import { unwrap } from "../../lib/session.server";
14
15export function meta({ params, ...args }: Route.MetaArgs) {
16 return page(args, { title: `Contributors · ${params.owner}/${params.repo} · g1t` });
17}
18
19export async function loader({ params, context }: Route.LoaderArgs) {
20 const { viewer } = await requireRepo(context, params, "read");
21 return { contributors: unwrap(await repos.contributors({ namespace: params.owner, name: params.repo }, viewer)) };
22}
23
24/** The most weeks a chart shows: the latest. */
25const WEEKS = 52;
26
27function weekLabel(week: string): string {
28 return new Date(`${week}T00:00:00Z`).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
29}
30
31/** Commits per week as bars, newest on the right, each with its week on hover. */
32function WeekBars({ weeks, values, height, label, scale }: { weeks: WeekCommits[]; values: number[]; height: number; label: string; scale?: number }) {
33 // One scale for every contributor, so their bars compare.
34 const top = scale ?? Math.max(1, ...values);
35 return (
36 <div role="img" aria-label={label} className="flex items-end gap-[2px]" style={{ height }}>
37 {values.map((value, at) => (
38 <Hint key={weeks[at].week} label={`${count(value, "commit")} · week of ${weekLabel(weeks[at].week)}`}>
39 <span className="flex h-full min-w-0 flex-1 items-end">
40 <span
41 className={`w-full rounded-t-[2px] ${value > 0 ? "bg-accent" : "bg-line"}`}
42 style={{ height: value > 0 ? `${Math.max(6, (value / top) * 100)}%` : 2 }}
43 />
44 </span>
45 </Hint>
46 ))}
47 </div>
48 );
49}
50
51function Person({ contributor, rank, weeks, scale }: { contributor: Contributor; rank: number; weeks: WeekCommits[]; scale: number }) {
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)52 // The same person, avatar, link and card as on their commits.
53 const person = contributorPerson(contributor);
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9754 return (
55 <li className="rounded-xl border border-line bg-surface p-4">
56 <div className="flex items-center gap-3">
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)57 <CommitAvatar person={person} size={36} />
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9758 <div className="min-w-0 grow">
59 <p className="flex items-center gap-2 truncate text-sm">
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)60 <CommitName person={person} className="font-medium" />
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9761 </p>
62 <p className="text-xs text-muted">
63 {count(contributor.commits, "commit")} · last <TimeAgo at={contributor.lastAt} />
64 </p>
65 </div>
66 <span className="text-xs text-faint tabular-nums">#{rank}</span>
67 </div>
68 {contributor.weeks.length > 0 && weeks.length > 0 && (
69 <div className="mt-3">
70 <WeekBars weeks={weeks} values={alignWeeks(weeks, contributor.weeks)} height={40} label={`${contributor.name}'s commits by week`} scale={scale} />
71 </div>
72 )}
73 </li>
74 );
75}
76
77export default function Contributors({ loaderData }: Route.ComponentProps) {
78 const { contributors } = loaderData;
79 const weeks = contributors.weeks.slice(-WEEKS);
80 const all = weeks.map((week) => week.commits);
81 const scale = Math.max(1, ...contributors.contributors.flatMap((contributor) => alignWeeks(weeks, contributor.weeks)));
82 return (
83 <div className="space-y-6">
84 <div>
85 <h2 className="text-lg font-semibold tracking-tight">
86 Contributors <span className="font-normal text-faint">{contributors.total}</span>
87 </h2>
88 <p className="mt-1 text-sm text-muted">
89 Commits on the default branch by who made them: people by the addresses they confirmed, g1t as itself, anyone else by
90 the name on their commits.
91 {contributors.partial && ` The newest ${contributors.commits.toLocaleString("en-US")} commits are counted.`}
92 </p>
93 </div>
94 {contributors.pending ? (
95 <EmptyState title="Reading the history">The default branch is being read for the first time. This page has it in a few seconds.</EmptyState>
96 ) : contributors.contributors.length === 0 ? (
97 <EmptyState title="No commits yet">Push a first commit and who made it shows here.</EmptyState>
98 ) : (
99 <>
100 <section className="rounded-xl border border-line bg-surface p-5">
101 <div className="mb-4 flex flex-wrap items-baseline justify-between gap-2">
102 <h3 className="text-sm font-semibold">Commits per week</h3>
103 <span className="text-xs text-muted">
104 {count(contributors.commits, "commit")} · peak {peak(weeks)} a week
105 </span>
106 </div>
107 <WeekBars weeks={weeks} values={all} height={120} label="Commits per week" />
108 <div className="mt-2 flex justify-between text-xs text-faint">
109 <span>{weeks[0] && weekLabel(weeks[0].week)}</span>
110 <span>{weeks.at(-1) && weekLabel(weeks.at(-1)!.week)}</span>
111 </div>
112 </section>
113 <ul className="grid gap-3 md:grid-cols-2">
114 {contributors.contributors.map((contributor, at) => (
115 <Person key={`${contributor.kind}:${contributor.name}`} contributor={contributor} rank={at + 1} weeks={weeks} scale={scale} />
116 ))}
117 </ul>
118 {contributors.computedAt && (
119 <p className="text-xs text-faint">
120 Counted <TimeAgo at={contributors.computedAt} />
121 {contributors.commit && contributors.head && contributors.commit !== contributors.head && ", and being counted again for the newest commits"}.
122 </p>
123 )}
124 </>
125 )}
126 </div>
127 );
128}

This file's history is long; its oldest lines are credited to the oldest commit read.