From 2ada0ae9e489a42c39b5f827651ed6a465e82b28 Mon Sep 17 00:00:00 2001 From: puppe1990 Date: Mon, 28 Sep 2026 10:50:29 -0300 Subject: [PATCH 1/2] Ignore .commandcode agent data Co-authored-by: CommandCodeBot --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4f86d12..f09c650 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ web/static/build/ node_modules/ tmp/ .air/ +.commandcode/ *.db .DS_Store From 45288bc1f57fd1e8a63c3ea6deafbcd5fa132d47 Mon Sep 17 00:00:00 2001 From: puppe1990 Date: Mon, 28 Sep 2026 10:50:38 -0300 Subject: [PATCH 2/2] Show traffic freshness in the catalog pulse The pulse could freeze silently whenever GitHub's traffic feed lagged past its usual ~1-2 day settle, and the chart gave no way to tell a stalled feed from an idle catalog. Report the newest day with recorded traffic under the chart, and warn once the feed is more than two days behind. Co-authored-by: CommandCodeBot --- web/src/components/CatalogCharts.svelte | 14 ++++++++- web/src/components/CatalogCharts.test.js | 24 +++++++++++++++- web/src/lib/trafficCharts.js | 35 +++++++++++++++++------ web/src/lib/trafficCharts.test.js | 36 ++++++++++++++++++++++++ web/static/css/atlas.css | 26 +++++++++++++++++ 5 files changed, 124 insertions(+), 11 deletions(-) diff --git a/web/src/components/CatalogCharts.svelte b/web/src/components/CatalogCharts.svelte index 5f4aca3..2cc0241 100644 --- a/web/src/components/CatalogCharts.svelte +++ b/web/src/components/CatalogCharts.svelte @@ -7,6 +7,7 @@ pulseBars, quietStarsRank, rankTraffic, + trafficFreshness, trafficRepos, uniqueVisitorsByLanguage, } from "../lib/trafficCharts.js"; @@ -18,7 +19,9 @@ $: totals = catalogTrafficTotals(repos); $: visitors = rankTraffic(repos, "view_uniques", 10); $: cloners = rankTraffic(repos, "clone_uniques", 10); - $: pulse = pulseBars(cutUnsettledDays(dailyCatalogPulse(repos))); + $: pulseDays = dailyCatalogPulse(repos); + $: freshness = trafficFreshness(pulseDays); + $: pulse = pulseBars(cutUnsettledDays(pulseDays)); $: languages = uniqueVisitorsByLanguage(repos); $: quiet = quietStarsRank(repos).map((row) => ({ ...row, @@ -64,6 +67,15 @@

Last 14 days

Unique visitors and cloners summed across this catalog. GitHub settles traffic with a ~1–2 day delay, so the newest days appear once they are final.

+

+ {#if !freshness.lastDay} + No traffic recorded in this window yet. + {:else if freshness.stale} + GitHub has not published new traffic since {freshness.label} ({freshness.daysBehind} days behind). + {:else} + Data through {freshness.label}. + {/if} +

{#each pulse as day} diff --git a/web/src/components/CatalogCharts.test.js b/web/src/components/CatalogCharts.test.js index 9336c31..a105d03 100644 --- a/web/src/components/CatalogCharts.test.js +++ b/web/src/components/CatalogCharts.test.js @@ -1,5 +1,5 @@ import { render, screen, within } from "@testing-library/svelte"; -import { describe, expect, test } from "vitest"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import CatalogCharts from "./CatalogCharts.svelte"; const repos = [ @@ -68,3 +68,25 @@ describe("CatalogCharts", () => { expect(within(pulse).getByTitle("1 unique cloner")).toBeInTheDocument(); }); }); + +describe("CatalogCharts freshness", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + test("reports the newest traffic day in the pulse", () => { + vi.setSystemTime(new Date("2026-08-29T12:00:00Z")); + render(CatalogCharts, { props: { repos } }); + expect(screen.getByText("Data through Aug 27.")).toBeInTheDocument(); + }); + + test("warns when GitHub lags past the settle delay", () => { + vi.setSystemTime(new Date("2026-08-30T12:00:00Z")); + render(CatalogCharts, { props: { repos } }); + expect(screen.getByText("GitHub has not published new traffic since Aug 27 (3 days behind).")).toBeInTheDocument(); + }); +}); diff --git a/web/src/lib/trafficCharts.js b/web/src/lib/trafficCharts.js index a292e5d..4504018 100644 --- a/web/src/lib/trafficCharts.js +++ b/web/src/lib/trafficCharts.js @@ -89,17 +89,34 @@ function pulseHeight(value, peak) { return `${Math.max(6, Math.round((value / peak) * 100))}%`; } +function utcDayLabel(date) { + return new Date(`${date}T00:00:00Z`).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + timeZone: "UTC", + }); +} + export function pulseBars(days) { const peak = Math.max(1, ...(days || []).flatMap((day) => [day.viewUniques, day.cloneUniques])); - return (days || []).map((day) => { - const utc = new Date(`${day.date}T00:00:00Z`); - return { - ...day, - label: utc.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" }), - viewHeight: pulseHeight(day.viewUniques, peak), - cloneHeight: pulseHeight(day.cloneUniques, peak), - }; - }); + return (days || []).map((day) => ({ + ...day, + label: utcDayLabel(day.date), + viewHeight: pulseHeight(day.viewUniques, peak), + cloneHeight: pulseHeight(day.cloneUniques, peak), + })); +} + +// GitHub can lag beyond its usual ~1-2 day settle, and the pulse then freezes +// silently; report the newest active day so the chart can show the real delay. +const FRESHNESS_LAG_DAYS = 2; + +export function trafficFreshness(days, now = new Date(), lagDays = FRESHNESS_LAG_DAYS) { + const lastDay = (days || []).findLast((day) => (day.viewUniques || 0) + (day.cloneUniques || 0) > 0); + if (!lastDay) return { lastDay: null, label: "", daysBehind: null, stale: false }; + const today = Date.parse(`${now.toISOString().slice(0, 10)}T00:00:00Z`); + const daysBehind = Math.round((today - Date.parse(`${lastDay.date}T00:00:00Z`)) / 864e5); + return { lastDay: lastDay.date, label: utcDayLabel(lastDay.date), daysBehind, stale: daysBehind > lagDays }; } export function uniqueVisitorsByLanguage(repos, limit = 8) { diff --git a/web/src/lib/trafficCharts.test.js b/web/src/lib/trafficCharts.test.js index 8d8f375..a8ceafd 100644 --- a/web/src/lib/trafficCharts.test.js +++ b/web/src/lib/trafficCharts.test.js @@ -6,6 +6,7 @@ import { pulseBars, quietStarsRank, rankTraffic, + trafficFreshness, trafficRepos, uniqueVisitorsByLanguage, } from "./trafficCharts.js"; @@ -148,6 +149,41 @@ describe("cutUnsettledDays", () => { }); }); +describe("trafficFreshness", () => { + const pulse = [ + { date: "2026-09-21", viewUniques: 4, cloneUniques: 3 }, + { date: "2026-09-22", viewUniques: 0, cloneUniques: 0 }, + { date: "2026-09-23", viewUniques: 2, cloneUniques: 0 }, + { date: "2026-09-24", viewUniques: 0, cloneUniques: 0 }, + { date: "2026-09-25", viewUniques: 0, cloneUniques: 0 }, + ]; + + test("reports the newest day with recorded traffic", () => { + expect(trafficFreshness(pulse, new Date("2026-09-25T12:00:00Z"))).toEqual({ + lastDay: "2026-09-23", + label: "Sep 23", + daysBehind: 2, + stale: false, + }); + }); + + test("flags a lagging feed once the gap passes the settle delay", () => { + const report = trafficFreshness(pulse, new Date("2026-09-28T12:00:00Z")); + expect(report.daysBehind).toBe(5); + expect(report.stale).toBe(true); + }); + + test("reports no active day when the window is all zeroes", () => { + const quiet = [{ date: "2026-09-25", viewUniques: 0, cloneUniques: 0 }]; + expect(trafficFreshness(quiet, new Date("2026-09-26T12:00:00Z"))).toEqual({ + lastDay: null, + label: "", + daysBehind: null, + stale: false, + }); + }); +}); + describe("pulseBars", () => { test("labels UTC days and scales both series to the same peak", () => { const bars = pulseBars(dailyCatalogPulse(catalog)); diff --git a/web/static/css/atlas.css b/web/static/css/atlas.css index 1db576b..b7ecd95 100644 --- a/web/static/css/atlas.css +++ b/web/static/css/atlas.css @@ -1340,6 +1340,32 @@ a.stat:hover { color: var(--ink-mute); } +.chart-freshness { + display: inline-flex; + align-items: center; + gap: 0.4rem; +} + +.chart-freshness::before { + content: ""; + width: 0.4rem; + height: 0.4rem; + border-radius: 999px; + background: var(--moss); +} + +.chart-freshness.is-stale { + color: var(--copper-hot); +} + +.chart-freshness.is-stale::before { + background: var(--copper); +} + +.chart-freshness.is-empty::before { + background: var(--ink-mute); +} + .rank-list { list-style: none; margin: 0;