Skip to content

Commit

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.

syntaqxcommitted Parent6e28591Browse files
8 files+65−220/8 viewed
+5−3
2222 | **Security policy** | A link to `SECURITY.md` at the root, or in `.g1t`, `.github` or `docs`. |
2323 | **Activity** | The repository's [activity](#activity): pushes, merges, new branches and tags. |
2424 | **Stars**, **watching** | How many people [starred](#stars) it, and how many watch all or some of its activity from the [Watch menu](/guides/notifications/). |
25−| **Releases** | How many [releases](/guides/releases/) it has and the latest, or **Create a new release** for people who can push. |
25+| **Releases** | How many [releases](/guides/releases/) it has and the latest. With none published, how many tags it has, linked to its tags, and **Create a new release** for people who can push. |
2626 | **Packages** | [Packages](/guides/packages/) linked to it, or how to publish the first. |
27−| **Contributors** | How many people and agents made it, and the most active. See [contributors](#contributors). |
27+| **Contributors** | How many people and agents made it. Up to five are listed by name with how many commits each made; more show as the most active's avatars. Each opens their card. See [contributors](#contributors). |
2828 | **Languages** | The languages it is written in, by bytes. See [languages](#languages). |
2929
3030 The license, security policy, languages and contributors are read from the
3636
3737 The bar counts the bytes of each language's files on the default branch.
3838 Programming and markup languages count; data such as JSON and YAML, and
39−prose such as Markdown, do not. Neither do:
39+prose such as Markdown, do not. TSX counts as TypeScript and JSX as
40+JavaScript. The sidebar lists every language down to a tenth of a percent,
41+the largest eight, and gathers the rest as **Other**. Neither do these count:
4042
4143 | Files | Such as |
4244 | --- | --- |
+37−5
2828
2929 import { compact, count, languageBar, licenseLabel } from "../lib/about";
3030 import { contributorPerson, profileHref, shownName } from "../lib/commit-people";
31−import { CommitAvatar } from "./commit-person";
31+import { CommitAvatar, CommitName } from "./commit-person";
3232 import { UserCard } from "./user-card";
3333 import { PackageIcon as EcosystemIcon } from "./package-icon";
3434 import { Topics } from "./topics";
3939
4040 /** How many contributors' avatars the About shows. */
4141 const AVATARS = 14;
42+/** Up to this many contributors are listed by name, one a row; more are shown as avatars. */
43+const LISTED = 5;
4244 /** How often, and how many times, it asks again while the default branch is first read. */
4345 const RETRY_MS = 3_000;
4446 const RETRIES = 6;
144146 return <p className="text-xs text-faint">Reading the default branch…</p>;
145147 }
146148
147−function Releases({ base, about, canPush }: { base: string; about: RepoAbout; canPush: boolean }) {
149+function Releases({ base, about, canPush, tags }: { base: string; about: RepoAbout; canPush: boolean; tags: number | null }) {
148150 const latest = about.latestRelease;
151+ // Tags, when no release names them yet: what is there to release from.
152+ const tagLine =
153+ tags && about.releases === 0 ? (
154+ <Link to={`${base}/tags`} className="flex items-center gap-1.5 text-sm font-medium text-fg hover:text-accent">
155+ <TagIcon size={14} className="text-muted" />
156+ {count(tags, "tag")}
157+ </Link>
158+ ) : null;
149159 return (
150160 <Section title="Releases" count={about.releases} to={`${base}/releases`}>
151161 {latest ? (
172182 </Link>
173183 ) : (
174184 <div className="text-sm text-muted">
175− <p>No releases published</p>
185+ {tagLine ?? <p>No releases published</p>}
176186 {canPush && (
177187 <Link to={`${base}/releases/new`} className="mt-1 inline-flex items-center gap-1 text-accent hover:underline">
178188 <Plus size={13} />
221231 <Reading />
222232 ) : shown.length === 0 ? (
223233 <p className="text-sm text-muted">No commits yet</p>
234+ ) : about.contributors <= LISTED ? (
235+ // A few people: each by name, as a row, with how much they did.
236+ <ul className="space-y-2">
237+ {shown.map((contributor) => {
238+ const person = contributorPerson(contributor);
239+ return (
240+ <li key={`${contributor.kind}:${contributor.username ?? contributor.name}`} className="flex min-w-0 items-center gap-2.5 text-sm">
241+ <CommitAvatar person={person} size={28} />
242+ <CommitName person={person} className="truncate font-medium text-fg" />
243+ <span className="ml-auto shrink-0 text-xs text-faint tabular-nums">{count(contributor.commits, "commit")}</span>
244+ </li>
245+ );
246+ })}
247+ </ul>
224248 ) : (
225249 <>
226250 <ul className="flex flex-wrap gap-1.5">
327351 first,
328352 canPush,
329353 packages,
354+ tags,
330355 deployments,
331356 }: {
332357 base: string;
333358 first: RepoAbout;
334359 canPush: boolean;
335360 packages: Promise<PackageSummary[] | null> | PackageSummary[] | null;
361+ tags: Promise<number | null> | number | null;
336362 deployments: ReactNode;
337363 }) {
338364 const about = useLive(base, first);
339365 return (
340366 <>
341− <Releases base={base} about={about} canPush={canPush} />
367+ <Suspense fallback={<Releases base={base} about={about} canPush={canPush} tags={null} />}>
368+ <Await resolve={tags} errorElement={<Releases base={base} about={about} canPush={canPush} tags={null} />}>
369+ {(found) => <Releases base={base} about={about} canPush={canPush} tags={found} />}
370+ </Await>
371+ </Suspense>
342372 <Suspense fallback={<SectionSkeleton title="Packages" />}>
343373 <Await resolve={packages} errorElement={<Packages packages={null} />}>
344374 {(found) => <Packages packages={found} />}
369399 watchers: Promise<number | null> | number | null;
370400 /** Packages published from it; null when they could not be read. */
371401 packages: Promise<PackageSummary[] | null> | PackageSummary[] | null;
402+ /** How many tags it has; null when they could not be counted. */
403+ tags?: Promise<number | null> | number | null;
372404 };
373405
374406 export function RepoAboutPanel({
443475 <Await resolve={data.about} errorElement={<>{deployments}</>}>
444476 {(about) =>
445477 about ? (
446− <LoadedSections base={base} first={about} canPush={canPush} packages={data.packages} deployments={deployments} />
478+ <LoadedSections base={base} first={about} canPush={canPush} packages={data.packages} tags={data.tags ?? null} deployments={deployments} />
447479 ) : (
448480 <>{deployments}</>
449481 )
+1−1
414414 repo={repo}
415415 gitRef={ref}
416416 readme={Boolean(readme)}
417− data={about ?? { about: null, watchers: null, packages: null }}
417+ data={about ?? { about: null, watchers: null, packages: null, tags: null }}
418418 canPush={canPush}
419419 homepage={homepage}
420420 deployments={<DeploymentsPanel base={base} summary={deployments} className="border-t border-line pt-4" />}
+2−1
11 /**
22 * The Files page's About, streamed in after the files: what the default
33 * branch says, stars and releases from repos; how many watch it from the
4− * inbox; packages published from it. Each is its own promise, so a slow
4+ * inbox; packages published from it; how many tags it has. Each is its own promise, so a slow
55 * one holds up only its own section, and a failure shows the section
66 * without it rather than failing the page.
77 */
2828 packages: within(
2929 packages.list(path.namespace, viewer, { repoId }).then((found): PackageSummary[] | null => (found.ok ? found.value : null)),
3030 ),
31+ tags: within(repos.tags(path, viewer).then((found) => (found.ok ? found.value.length : null))),
3132 };
3233 }
+10−3
1919 assert.equal(count(3, "watching", "watching"), "3 watching");
2020 });
2121
22−test("small languages are gathered as Other", () => {
22+test("every language down to a tenth of a percent is listed, at most eight; the rest are Other", () => {
2323 const rust = { name: "Rust", color: "#dea584", bytes: 900, percent: 90 };
2424 const shell = { name: "Shell", color: "#89e051", bytes: 95, percent: 9.5 };
25− const nix = { name: "Nix", color: "#7e7eff", bytes: 5, percent: 0.5 };
25+ const nix = { name: "Nix", color: "#7e7eff", bytes: 5, percent: 0.05 };
26+ const css = { name: "CSS", color: "#663399", bytes: 3, percent: 0.3 };
27+ assert.deepEqual(languageBar([rust, css, shell]).map((l) => l.name), ["Rust", "Shell", "CSS"], "sorted, and a 0.3% language is shown");
28+ const many = Array.from({ length: 10 }, (_, i) => ({ name: `L${i}`, color: null, bytes: 10 - i, percent: 10 - i }));
29+ const capped = languageBar(many);
30+ assert.equal(capped.length, 9);
31+ assert.equal(capped[8]!.name, "Other");
32+ assert.equal(capped[8]!.percent, 3);
2633 assert.deepEqual(languageBar([rust, shell]), [rust, shell]);
2734 const bar = languageBar([rust, shell, nix]);
2835 assert.equal(bar.length, 3);
2936 assert.equal(bar[2].name, "Other");
30− assert.equal(bar[2].percent, 0.5);
37+ assert.equal(bar[2].percent, 0.1);
3138 assert.deepEqual(languageBar([rust, { ...nix, percent: 0 }]), [rust], "nothing to show for nothing");
3239 assert.deepEqual(languageBar([]), []);
3340 });
+5−3
3131 }
3232
3333 /** The language bar's pieces: the largest, then the rest under 1% as Other. */
34−export function languageBar(languages: LanguageShare[], least = 1): LanguageShare[] {
35− const shown = languages.filter((language) => language.percent >= least);
36− const rest = languages.filter((language) => language.percent < least);
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));
3739 if (rest.length === 0) return shown;
3840 const bytes = rest.reduce((sum, language) => sum + language.bytes, 0);
3941 const percent = Math.round(rest.reduce((sum, language) => sum + language.percent, 0) * 10) / 10;
+3−4
110110 lang("Svelte", "#ff3e00", Markup),
111111 lang("Swift", "#F05138", Programming),
112112 lang("TOML", "#9c4221", Data),
113− lang("TSX", "#3178c6", Programming),
114113 lang("Twig", "#c1d026", Markup),
115114 lang("TypeScript", "#3178c6", Programming),
116115 lang("Vim Script", "#199f4b", Programming),
214213 "svelte" => "Svelte",
215214 "swift" => "Swift",
216215 "toml" => "TOML",
217− "tsx" => "TSX",
218216 "twig" => "Twig",
219− "ts" | "mts" | "cts" => "TypeScript",
217+ // TSX is TypeScript, as JSX is JavaScript: one language on the bar.
218+ "ts" | "tsx" | "mts" | "cts" => "TypeScript",
220219 "vim" => "Vim Script",
221220 "vb" => "Visual Basic .NET",
222221 "vue" => "Vue",
431430 #[test]
432431 fn files_are_known_by_extension_and_name() {
433432 assert_eq!(name("src/main.rs"), Some("Rust"));
434− assert_eq!(name("app/routes/home.tsx"), Some("TSX"));
433+ assert_eq!(name("app/routes/home.tsx"), Some("TypeScript"));
435434 assert_eq!(name("lib/x.mts"), Some("TypeScript"));
436435 assert_eq!(name("Dockerfile"), Some("Dockerfile"));
437436 assert_eq!(name("deploy/Dockerfile.prod"), Some("Dockerfile"));
+2−2
4646 /// Where a security policy may be.
4747 const POLICY_DIRS: [&str; 4] = ["", ".g1t/", ".github/", "docs/"];
4848
49−/// How the About is worked out (2: coding agents are contributors of their own); a row kept by an older version is worked
49+/// How the About is worked out (2: coding agents are contributors of their own; 3: TSX counts as TypeScript); a row kept by an older version is worked
5050 /// out again. 1: g1t's older commit addresses count as g1t.
51−pub const STATS_VERSION: f64 = 2.0;
51+pub const STATS_VERSION: f64 = 3.0;
5252
5353 /// What `repo_stats` keeps for the About, without the full contributors.
5454 #[derive(Debug, Default, Deserialize)]