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. */ | |
| Merge remote-tracking branch 'origin/main' into claude/cool-newton-7nm8r9 | 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 } | |
| Merge remote-tracking branch 'origin/main' into claude/cool-newton-7nm8r9 | 61 | | { kind: "deleted"; branch: string; commit: string } |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 62 | | { kind: "tag"; tag: string; commit: string } |
| 63 | | { kind: "merge"; number: number; commit: string } | |
| 64 | | { kind: "renamed"; from: string; to: string } | |
| 65 | | { kind: "default"; from: string; to: string }; | |
| 66 | ||
| 67 | /** The events the Activity page lists. */ | |
| Merge remote-tracking branch 'origin/main' into claude/cool-newton-7nm8r9 | 68 | export const ACTIVITY_TYPES = ["git.push", "git.deleted", "pull.merged", "branch.renamed", "repo.default_branch_changed"] as const; |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 69 | |
| 70 | /** One event as an Activity line, or null for one it does not list. */ | |
| 71 | export function activityLine(event: G1tEvent): ActivityLine | null { | |
| 72 | switch (event.type) { | |
| 73 | case "git.push": { | |
| 74 | const data = event.data as { ref: string; before?: string | null; after: string; defaultBranch: boolean }; | |
| 75 | if (data.ref.startsWith("refs/tags/")) return { kind: "tag", tag: data.ref.slice("refs/tags/".length), commit: data.after }; | |
| 76 | if (!data.ref.startsWith("refs/heads/")) return null; | |
| 77 | return { | |
| 78 | kind: "push", | |
| 79 | branch: data.ref.slice("refs/heads/".length), | |
| 80 | commit: data.after, | |
| 81 | before: data.before ?? null, | |
| 82 | created: !data.before, | |
| 83 | defaultBranch: data.defaultBranch, | |
| 84 | }; | |
| 85 | } | |
| Merge remote-tracking branch 'origin/main' into claude/cool-newton-7nm8r9 | 86 | case "git.deleted": { |
| 87 | // A deleted tag is not listed; a deleted branch is, with the commit | |
| 88 | // it pointed to, which restores it. | |
| 89 | const data = event.data as { ref: string; before: string }; | |
| 90 | if (!data.ref.startsWith("refs/heads/")) return null; | |
| 91 | return { kind: "deleted", branch: data.ref.slice("refs/heads/".length), commit: data.before }; | |
| 92 | } | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 93 | case "pull.merged": { |
| 94 | const data = event.data as { number: number; commit: string }; | |
| 95 | return { kind: "merge", number: data.number, commit: data.commit }; | |
| 96 | } | |
| 97 | case "branch.renamed": { | |
| 98 | const data = event.data as { from: string; to: string }; | |
| 99 | return { kind: "renamed", from: data.from, to: data.to }; | |
| 100 | } | |
| 101 | case "repo.default_branch_changed": { | |
| 102 | const data = event.data as { from: string; to: string; renamed: boolean }; | |
| 103 | // A rename of the default branch says so once, as a rename. | |
| 104 | return data.renamed ? null : { kind: "default", from: data.from, to: data.to }; | |
| 105 | } | |
| 106 | default: | |
| 107 | return null; | |
| 108 | } | |
| 109 | } | |
| Merge remote-tracking branch 'origin/main' into claude/cool-newton-7nm8r9 | 110 | |
| 111 | /** | |
| 112 | * Whether an Activity line is about `branch`: pushed to, made, deleted or | |
| 113 | * renamed, or made or replaced as the default. Merges land on the default | |
| 114 | * branch (`defaultBranch`). | |
| 115 | */ | |
| 116 | export function aboutBranch(line: ActivityLine, branch: string, defaultBranch: string): boolean { | |
| 117 | switch (line.kind) { | |
| 118 | case "push": | |
| 119 | case "deleted": | |
| 120 | return line.branch === branch; | |
| 121 | case "tag": | |
| 122 | return false; | |
| 123 | case "merge": | |
| 124 | return branch === defaultBranch; | |
| 125 | case "renamed": | |
| 126 | case "default": | |
| 127 | return line.from === branch || line.to === branch; | |
| 128 | } | |
| 129 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.