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-a69aeabc4b0deeb97 | 1 | /** |
| 2 | * What the Files page's About, the Contributors, Releases and Activity | |
| 3 | * pages say, worked out without the server: labels, the language bar, | |
| 4 | * and an activity line for each event worth one. | |
| 5 | */ | |
| Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016) | 6 | import type { G1tEvent, LanguageShare, License, Project, WeekCommits } from "@g1t/contracts"; |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 7 | |
| 8 | /** | |
| 9 | * The address the About links to: the project's homepage (its own, or its | |
| 10 | * repository's website it follows), else production when it runs | |
| 11 | * elsewhere. Null leaves the repository's website. | |
| 12 | */ | |
| 13 | export function projectHomepage(project: Pick<Project, "links" | "runs" | "productionUrl"> | null): string | null { | |
| 14 | if (!project) return null; | |
| 15 | return project.links?.homepage ?? (project.runs === "elsewhere" ? project.productionUrl : null) ?? null; | |
| 16 | } | |
| 17 | ||
| 18 | /** "MIT license", as the About names it; "View license" for one not recognized. */ | |
| 19 | export function licenseLabel(license: License): string { | |
| 20 | return license.spdxId ? `${license.spdxId} license` : "View license"; | |
| 21 | } | |
| 22 | ||
| 23 | /** "12", "1.2k", "15k". */ | |
| 24 | export function compact(n: number): string { | |
| 25 | return n >= 10_000 ? `${Math.round(n / 1000)}k` : n >= 1000 ? `${(n / 1000).toFixed(1).replace(/\.0$/, "")}k` : String(n); | |
| 26 | } | |
| 27 | ||
| 28 | /** "1 star", "1.2k stars". */ | |
| 29 | export function count(n: number, one: string, many = `${one}s`): string { | |
| 30 | return `${compact(n)} ${n === 1 ? one : many}`; | |
| 31 | } | |
| 32 | ||
| 33 | /** The language bar's pieces: the largest, then the rest under 1% as Other. */ | |
| The repository sidebar reads like it should. TSX counts as TypeScript, as JSX already counted as JavaScript, so one language is not split in two (stats version 3, so every repository is read again); the languages list every one down to a tenth of a percent, the largest eight, rather than folding MDX, CSS and Shell into Other; up to five contributors are listed by name, each with their commits and card, more as avatars; and with no release published, Releases says how many tags there are, linked to them. | 34 | export function languageBar(languages: LanguageShare[], least = 0.1, most = 8): LanguageShare[] { |
| 35 | // Every language down to a tenth of a percent, the largest `most` of them; the rest as Other. | |
| 36 | const sorted = [...languages].sort((a, b) => b.percent - a.percent); | |
| 37 | const shown = sorted.filter((language, index) => language.percent >= least && index < most); | |
| 38 | const rest = sorted.filter((language) => !shown.includes(language)); | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 39 | if (rest.length === 0) return shown; |
| 40 | const bytes = rest.reduce((sum, language) => sum + language.bytes, 0); | |
| 41 | const percent = Math.round(rest.reduce((sum, language) => sum + language.percent, 0) * 10) / 10; | |
| 42 | if (percent === 0 && shown.length > 0) return shown; | |
| 43 | return [...shown, { name: "Other", color: null, bytes, percent }]; | |
| 44 | } | |
| 45 | ||
| 46 | ||
| 47 | /** The tallest week, for a chart's scale: never zero. */ | |
| 48 | export function peak(weeks: WeekCommits[]): number { | |
| 49 | return Math.max(1, ...weeks.map((week) => week.commits)); | |
| 50 | } | |
| 51 | ||
| 52 | /** A contributor's weeks laid over the repository's, missing weeks as none. */ | |
| 53 | export function alignWeeks(all: WeekCommits[], own: WeekCommits[]): number[] { | |
| 54 | const byWeek = new Map(own.map((week) => [week.week, week.commits])); | |
| 55 | return all.map((week) => byWeek.get(week.week) ?? 0); | |
| 56 | } | |
| 57 | ||
| 58 | /** An Activity line: what happened, and the branch, tag or pull request it was to. */ | |
| 59 | export type ActivityLine = | |
| 60 | | { kind: "push"; branch: string; commit: string; before: string | null; created: boolean; defaultBranch: boolean } | |
| 61 | | { kind: "tag"; tag: string; commit: string } | |
| 62 | | { kind: "merge"; number: number; commit: string } | |
| 63 | | { kind: "renamed"; from: string; to: string } | |
| 64 | | { kind: "default"; from: string; to: string }; | |
| 65 | ||
| 66 | /** The events the Activity page lists. */ | |
| 67 | export const ACTIVITY_TYPES = ["git.push", "pull.merged", "branch.renamed", "repo.default_branch_changed"] as const; | |
| 68 | ||
| 69 | /** One event as an Activity line, or null for one it does not list. */ | |
| 70 | export function activityLine(event: G1tEvent): ActivityLine | null { | |
| 71 | switch (event.type) { | |
| 72 | case "git.push": { | |
| 73 | const data = event.data as { ref: string; before?: string | null; after: string; defaultBranch: boolean }; | |
| 74 | if (data.ref.startsWith("refs/tags/")) return { kind: "tag", tag: data.ref.slice("refs/tags/".length), commit: data.after }; | |
| 75 | if (!data.ref.startsWith("refs/heads/")) return null; | |
| 76 | return { | |
| 77 | kind: "push", | |
| 78 | branch: data.ref.slice("refs/heads/".length), | |
| 79 | commit: data.after, | |
| 80 | before: data.before ?? null, | |
| 81 | created: !data.before, | |
| 82 | defaultBranch: data.defaultBranch, | |
| 83 | }; | |
| 84 | } | |
| 85 | case "pull.merged": { | |
| 86 | const data = event.data as { number: number; commit: string }; | |
| 87 | return { kind: "merge", number: data.number, commit: data.commit }; | |
| 88 | } | |
| 89 | case "branch.renamed": { | |
| 90 | const data = event.data as { from: string; to: string }; | |
| 91 | return { kind: "renamed", from: data.from, to: data.to }; | |
| 92 | } | |
| 93 | case "repo.default_branch_changed": { | |
| 94 | const data = event.data as { from: string; to: string; renamed: boolean }; | |
| 95 | // A rename of the default branch says so once, as a rename. | |
| 96 | return data.renamed ? null : { kind: "default", from: data.from, to: data.to }; | |
| 97 | } | |
| 98 | default: | |
| 99 | return null; | |
| 100 | } | |
| 101 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.