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 } | |
| The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days. | 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. */ | |
| The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days. | 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 | } | |
| The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days. | 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 | } | |
| The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days. | 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.