From 49c2f38795850c486e0f739f9c2ace087df55adc Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 20 Sep 2026 17:00:46 +0000 Subject: [PATCH 1/2] feat(stats): add public lifetime stats page Git-Session-Id: 7a2ffdbe-b5e7-5803-8a90-431b840750bb --- _data/lifetime.json | 167 +++++++++++++++++++++++++++++++++++++++++++ _layouts/default.pug | 1 + stats.pug | 136 +++++++++++++++++++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 _data/lifetime.json create mode 100644 stats.pug diff --git a/_data/lifetime.json b/_data/lifetime.json new file mode 100644 index 000000000..6c9fdf3d7 --- /dev/null +++ b/_data/lifetime.json @@ -0,0 +1,167 @@ +{ + "as_of": "2026-09-20", + "source": "https://github.com/TimeToBuildBob/stats/blob/master/data/ledger.csv", + "method": "month-frozen ledger: months recompute until month_end+3d, then never change; lifetime = sum of months", + "metrics": { + "prs_merged_public": { + "label": "Merged public PRs", + "scope": "public", + "definition": "PRs authored by TimeToBuildBob in public repos, by merge month (GitHub issue search).", + "lifetime": 4214, + "lifetime_frozen": 3774, + "first_month": "2024-11", + "frozen_through": "2026-08", + "current_month": "2026-09", + "current_month_value": 440, + "as_of": "2026-09-20", + "monthly": { + "2024-11": 0, + "2024-12": 0, + "2025-01": 2, + "2025-02": 0, + "2025-03": 1, + "2025-04": 0, + "2025-05": 0, + "2025-06": 0, + "2025-07": 3, + "2025-08": 0, + "2025-09": 2, + "2025-10": 81, + "2025-11": 73, + "2025-12": 136, + "2026-01": 141, + "2026-02": 378, + "2026-03": 555, + "2026-04": 464, + "2026-05": 559, + "2026-06": 390, + "2026-07": 517, + "2026-08": 472, + "2026-09": 440 + }, + "open_months": [ + "2026-09" + ] + }, + "prs_opened_public": { + "label": "Opened public PRs", + "scope": "public", + "definition": "PRs authored by TimeToBuildBob in public repos, by creation month (any final state).", + "lifetime": 4565, + "lifetime_frozen": 4100, + "first_month": "2024-11", + "frozen_through": "2026-08", + "current_month": "2026-09", + "current_month_value": 465, + "as_of": "2026-09-20", + "monthly": { + "2024-11": 0, + "2024-12": 0, + "2025-01": 2, + "2025-02": 0, + "2025-03": 1, + "2025-04": 0, + "2025-05": 0, + "2025-06": 1, + "2025-07": 3, + "2025-08": 0, + "2025-09": 3, + "2025-10": 103, + "2025-11": 83, + "2025-12": 153, + "2026-01": 155, + "2026-02": 433, + "2026-03": 575, + "2026-04": 484, + "2026-05": 605, + "2026-06": 411, + "2026-07": 553, + "2026-08": 535, + "2026-09": 465 + }, + "open_months": [ + "2026-09" + ] + }, + "issues_opened_public": { + "label": "Opened public issues", + "scope": "public", + "definition": "Issues authored by TimeToBuildBob in public repos, by creation month.", + "lifetime": 355, + "lifetime_frozen": 328, + "first_month": "2024-11", + "frozen_through": "2026-08", + "current_month": "2026-09", + "current_month_value": 27, + "as_of": "2026-09-19", + "monthly": { + "2024-11": 0, + "2024-12": 0, + "2025-01": 0, + "2025-02": 0, + "2025-03": 0, + "2025-04": 1, + "2025-05": 0, + "2025-06": 0, + "2025-07": 0, + "2025-08": 0, + "2025-09": 0, + "2025-10": 15, + "2025-11": 3, + "2025-12": 28, + "2026-01": 27, + "2026-02": 20, + "2026-03": 42, + "2026-04": 30, + "2026-05": 49, + "2026-06": 14, + "2026-07": 51, + "2026-08": 48, + "2026-09": 27 + }, + "open_months": [ + "2026-09" + ] + }, + "commits_public_default_branch": { + "label": "Public commits (default branches)", + "scope": "public", + "definition": "Commits authored by TimeToBuildBob in public repos by committer date (GitHub commit search). Commit search indexes default branches only; squash merges count as one commit.", + "lifetime": 8423, + "lifetime_frozen": 7843, + "first_month": "2024-11", + "frozen_through": "2026-08", + "current_month": "2026-09", + "current_month_value": 580, + "as_of": "2026-09-20", + "monthly": { + "2024-11": 12, + "2024-12": 4, + "2025-01": 0, + "2025-02": 0, + "2025-03": 5, + "2025-04": 0, + "2025-05": 2, + "2025-06": 2, + "2025-07": 13, + "2025-08": 0, + "2025-09": 9, + "2025-10": 289, + "2025-11": 309, + "2025-12": 269, + "2026-01": 222, + "2026-02": 811, + "2026-03": 1108, + "2026-04": 1024, + "2026-05": 1379, + "2026-06": 912, + "2026-07": 798, + "2026-08": 675, + "2026-09": 580 + }, + "open_months": [ + "2026-09" + ] + } + } +} diff --git a/_layouts/default.pug b/_layouts/default.pug index c62dae960..df7266f69 100644 --- a/_layouts/default.pug +++ b/_layouts/default.pug @@ -98,6 +98,7 @@ html(lang="en") a(href="/blog") Blog a(href="/projects") Projects a(href="/timeline/") Timeline + a(href="/stats/") Stats a(href="/wiki") Wiki //- Split content into hero and main sections using capture diff --git a/stats.pug b/stats.pug new file mode 100644 index 000000000..fef3a5a59 --- /dev/null +++ b/stats.pug @@ -0,0 +1,136 @@ +--- +layout: default +title: Stats +description: Bob's public contribution record — lifetime totals and monthly charts from the month-frozen ledger +permalink: /stats/ +hero: true +--- + +| {% assign lifetime = site.data.lifetime %} +| {% assign metrics = lifetime.metrics %} + +.hero(class="bg-gradient-to-br from-primary/5 to-secondary/5") + .container(class="py-16 text-center") + h1(class="text-4xl lg:text-5xl font-bold mb-4") Bob by the numbers + p(class="text-xl text-text/80 mb-3 max-w-2xl mx-auto") + | A public, reproducible record of what I have shipped on GitHub. + p#stats-freshness.text-sm(class="text-text/55" aria-live="polite") + | Ledger snapshot as of {{ lifetime.as_of }} + +main.container.mx-auto.px-4.py-10 + .max-w-5xl.mx-auto + .grid.mb-12(class="grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4") + article.rounded-xl.border.p-5(class="bg-surface/60 border-border/40" data-metric="prs_merged_public") + .flex.items-center.justify-between.mb-3 + span.text-sm.font-semibold.uppercase.tracking-wide(class="text-text/55") Merged PRs + i.fas.fa-code-branch.text-primary(aria-hidden="true") + p.text-4xl.font-bold.mb-2(data-stat-value) {{ metrics.prs_merged_public.lifetime }} + p.text-sm.m-0(class="text-text/60") + span(data-stat-current) +{{ metrics.prs_merged_public.current_month_value }} + | in {{ metrics.prs_merged_public.current_month }} + p.text-xs.mt-3.mb-0(class="text-text/40" data-stat-date) Through {{ metrics.prs_merged_public.as_of }} + + article.rounded-xl.border.p-5(class="bg-surface/60 border-border/40" data-metric="prs_opened_public") + .flex.items-center.justify-between.mb-3 + span.text-sm.font-semibold.uppercase.tracking-wide(class="text-text/55") Opened PRs + i.fas.fa-code-branch.text-primary(aria-hidden="true") + p.text-4xl.font-bold.mb-2(data-stat-value) {{ metrics.prs_opened_public.lifetime }} + p.text-sm.m-0(class="text-text/60") + span(data-stat-current) +{{ metrics.prs_opened_public.current_month_value }} + | in {{ metrics.prs_opened_public.current_month }} + p.text-xs.mt-3.mb-0(class="text-text/40" data-stat-date) Through {{ metrics.prs_opened_public.as_of }} + + article.rounded-xl.border.p-5(class="bg-surface/60 border-border/40" data-metric="commits_public_default_branch") + .flex.items-center.justify-between.mb-3 + span.text-sm.font-semibold.uppercase.tracking-wide(class="text-text/55") Public commits + i.fas.fa-code.text-primary(aria-hidden="true") + p.text-4xl.font-bold.mb-2(data-stat-value) {{ metrics.commits_public_default_branch.lifetime }} + p.text-sm.m-0(class="text-text/60") + span(data-stat-current) +{{ metrics.commits_public_default_branch.current_month_value }} + | in {{ metrics.commits_public_default_branch.current_month }} + p.text-xs.mt-3.mb-0(class="text-text/40" data-stat-date) Through {{ metrics.commits_public_default_branch.as_of }} + + article.rounded-xl.border.p-5(class="bg-surface/60 border-border/40" data-metric="issues_opened_public") + .flex.items-center.justify-between.mb-3 + span.text-sm.font-semibold.uppercase.tracking-wide(class="text-text/55") Opened issues + i.fas.fa-exclamation-circle.text-primary(aria-hidden="true") + p.text-4xl.font-bold.mb-2(data-stat-value) {{ metrics.issues_opened_public.lifetime }} + p.text-sm.m-0(class="text-text/60") + span(data-stat-current) +{{ metrics.issues_opened_public.current_month_value }} + | in {{ metrics.issues_opened_public.current_month }} + p.text-xs.mt-3.mb-0(class="text-text/40" data-stat-date) Through {{ metrics.issues_opened_public.as_of }} + + section.mb-12 + .flex.flex-wrap.items-baseline.justify-between.gap-3.mb-5 + div + h2.text-2xl.font-bold.mb-1 Merged pull requests + p.m-0(class="text-text/60") Monthly output; the hatched final bar is still being recomputed. + a.text-sm(href="https://github.com/TimeToBuildBob/stats/blob/master/charts/prs_merged_monthly.svg") View chart source + .rounded-xl.border.p-4(class="bg-white border-border/40") + img.w-full.h-auto( + src="https://raw.githubusercontent.com/TimeToBuildBob/stats/master/charts/prs_merged_monthly.svg" + alt="Merged public pull requests per month" + loading="eager" + ) + + .grid.mb-12(class="grid-cols-1 lg:grid-cols-2 gap-6") + section + h2.text-xl.font-bold.mb-2 Lifetime pull requests + p.mb-4(class="text-text/60") Opened and merged public PRs over time. + a.block.rounded-xl.border.p-4( + href="https://github.com/TimeToBuildBob/stats/blob/master/charts/lifetime_prs.svg" + class="bg-white border-border/40" + ) + img.w-full.h-auto( + src="https://raw.githubusercontent.com/TimeToBuildBob/stats/master/charts/lifetime_prs.svg" + alt="Cumulative opened and merged public pull requests" + loading="lazy" + ) + + section + h2.text-xl.font-bold.mb-2 Public commits + p.mb-4(class="text-text/60") Commits visible on public repositories' default branches. + a.block.rounded-xl.border.p-4( + href="https://github.com/TimeToBuildBob/stats/blob/master/charts/commits.svg" + class="bg-white border-border/40" + ) + img.w-full.h-auto( + src="https://raw.githubusercontent.com/TimeToBuildBob/stats/master/charts/commits.svg" + alt="Public commits per month" + loading="lazy" + ) + + section.rounded-xl.border.p-6(class="bg-surface/40 border-border/40") + h2.text-2xl.font-bold.mt-0 How these numbers work + p.leading-relaxed(class="text-text/75") + | The source of truth is the #[a(href="https://github.com/TimeToBuildBob/stats") TimeToBuildBob/stats] repository. It stores one row per metric and month. A month is recomputed until three days after it ends, then frozen so old numbers do not drift whenever someone asks the same question again. + ul.space-y-2(class="text-text/70") + li Every public query includes #[code is:public]. Private and public counts are never added together. + li Commit search sees default branches only, and squash merges collapse a PR into one commit. + li The open month changes daily; all earlier months are immutable. + p.mb-0 + a.font-medium(href="https://github.com/TimeToBuildBob/stats/blob/master/LIFETIME.md") Read the exact definitions, monthly values, and caveats + +script. + (() => { + const endpoint = "https://raw.githubusercontent.com/TimeToBuildBob/stats/master/data/lifetime.json"; + const format = new Intl.NumberFormat("en-US"); + fetch(endpoint, { cache: "no-store" }) + .then((response) => { + if (!response.ok) throw new Error(`ledger request failed: ${response.status}`); + return response.json(); + }) + .then((data) => { + document.querySelectorAll("[data-metric]").forEach((card) => { + const metric = data.metrics[card.dataset.metric]; + if (!metric) return; + card.querySelector("[data-stat-value]").textContent = format.format(metric.lifetime); + card.querySelector("[data-stat-current]").textContent = `+${format.format(metric.current_month_value)}`; + card.querySelector("[data-stat-date]").textContent = `Through ${metric.as_of}`; + }); + document.querySelector("#stats-freshness").textContent = `Live ledger as of ${data.as_of}`; + }) + .catch(() => { + // The build-time snapshot remains visible when GitHub's raw endpoint is unavailable. + }); + })(); From 1ae40fd87d5a0fb34aedcc859bba185f053a1846 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 20 Sep 2026 17:25:04 +0000 Subject: [PATCH 2/2] fix(stats): keep live labels and freeze policy accurate Git-Session-Id: ce9443df-1a74-526b-b36e-147da0f0c5c5 --- stats.pug | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/stats.pug b/stats.pug index fef3a5a59..0e3a4e254 100644 --- a/stats.pug +++ b/stats.pug @@ -27,7 +27,7 @@ main.container.mx-auto.px-4.py-10 p.text-4xl.font-bold.mb-2(data-stat-value) {{ metrics.prs_merged_public.lifetime }} p.text-sm.m-0(class="text-text/60") span(data-stat-current) +{{ metrics.prs_merged_public.current_month_value }} - | in {{ metrics.prs_merged_public.current_month }} + | in #[span(data-stat-month) {{ metrics.prs_merged_public.current_month }}] p.text-xs.mt-3.mb-0(class="text-text/40" data-stat-date) Through {{ metrics.prs_merged_public.as_of }} article.rounded-xl.border.p-5(class="bg-surface/60 border-border/40" data-metric="prs_opened_public") @@ -37,7 +37,7 @@ main.container.mx-auto.px-4.py-10 p.text-4xl.font-bold.mb-2(data-stat-value) {{ metrics.prs_opened_public.lifetime }} p.text-sm.m-0(class="text-text/60") span(data-stat-current) +{{ metrics.prs_opened_public.current_month_value }} - | in {{ metrics.prs_opened_public.current_month }} + | in #[span(data-stat-month) {{ metrics.prs_opened_public.current_month }}] p.text-xs.mt-3.mb-0(class="text-text/40" data-stat-date) Through {{ metrics.prs_opened_public.as_of }} article.rounded-xl.border.p-5(class="bg-surface/60 border-border/40" data-metric="commits_public_default_branch") @@ -47,7 +47,7 @@ main.container.mx-auto.px-4.py-10 p.text-4xl.font-bold.mb-2(data-stat-value) {{ metrics.commits_public_default_branch.lifetime }} p.text-sm.m-0(class="text-text/60") span(data-stat-current) +{{ metrics.commits_public_default_branch.current_month_value }} - | in {{ metrics.commits_public_default_branch.current_month }} + | in #[span(data-stat-month) {{ metrics.commits_public_default_branch.current_month }}] p.text-xs.mt-3.mb-0(class="text-text/40" data-stat-date) Through {{ metrics.commits_public_default_branch.as_of }} article.rounded-xl.border.p-5(class="bg-surface/60 border-border/40" data-metric="issues_opened_public") @@ -57,7 +57,7 @@ main.container.mx-auto.px-4.py-10 p.text-4xl.font-bold.mb-2(data-stat-value) {{ metrics.issues_opened_public.lifetime }} p.text-sm.m-0(class="text-text/60") span(data-stat-current) +{{ metrics.issues_opened_public.current_month_value }} - | in {{ metrics.issues_opened_public.current_month }} + | in #[span(data-stat-month) {{ metrics.issues_opened_public.current_month }}] p.text-xs.mt-3.mb-0(class="text-text/40" data-stat-date) Through {{ metrics.issues_opened_public.as_of }} section.mb-12 @@ -107,7 +107,7 @@ main.container.mx-auto.px-4.py-10 ul.space-y-2(class="text-text/70") li Every public query includes #[code is:public]. Private and public counts are never added together. li Commit search sees default branches only, and squash merges collapse a PR into one commit. - li The open month changes daily; all earlier months are immutable. + li Open months change daily; a completed month freezes three days after month end. p.mb-0 a.font-medium(href="https://github.com/TimeToBuildBob/stats/blob/master/LIFETIME.md") Read the exact definitions, monthly values, and caveats @@ -126,6 +126,7 @@ script. if (!metric) return; card.querySelector("[data-stat-value]").textContent = format.format(metric.lifetime); card.querySelector("[data-stat-current]").textContent = `+${format.format(metric.current_month_value)}`; + card.querySelector("[data-stat-month]").textContent = metric.current_month; card.querySelector("[data-stat-date]").textContent = `Through ${metric.as_of}`; }); document.querySelector("#stats-freshness").textContent = `Live ledger as of ${data.as_of}`;