From b287561b08ccae6915cff55c1714dd3fb40a03ef Mon Sep 17 00:00:00 2001 From: Eason WaveKat Date: Sun, 17 May 2026 20:56:40 +1200 Subject: [PATCH 1/2] fix(star-tracker): de-inflate chart-view counts + per-repo breakdown The chart preview rendered two tags (light + dark) with CSS toggling visibility, so every page load fetched both variants and doubled the logged chart-view count. Switch to one swapped by JS, and switch the landing-page auto-preview to so only the matching theme actually fetches. Also add a "Views by destination" breakdown in the Audience panel, grouping by (repo, kind) so the owner can see which org/repo pages and embedded charts are getting traffic. Co-Authored-By: Claude Opus 4.7 (1M context) --- tools/star-tracker/src/db.ts | 14 +++++++++ tools/star-tracker/src/pages.ts | 54 +++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/tools/star-tracker/src/db.ts b/tools/star-tracker/src/db.ts index 8a78ac5..fcabe5a 100644 --- a/tools/star-tracker/src/db.ts +++ b/tools/star-tracker/src/db.ts @@ -577,6 +577,10 @@ export type ViewsSummary = { topReferers: { host: string; count: number }[]; // chart + page combined topCountries: { country: string; count: number }[]; daily: { day: string; chart: number; page: number }[]; // ascending day + // Per-destination breakdown. repo='' rows are the tenant-level + // (org-wide) chart/page; repo='/' are per-repo. Sorted + // by total (chart+page) desc. + byDestination: { repo: string; chart: number; page: number }[]; }; // Compact analytics summary used by the owner's tenant page. One pass of @@ -615,6 +619,7 @@ export async function viewsSummary( const refs = new Map(); const countries = new Map(); const dailyMap = new Map(); + const destMap = new Map(); for (const r of results ?? []) { if (r.kind === 'chart') { @@ -631,6 +636,10 @@ export async function viewsSummary( if (r.kind === 'chart') d.chart += r.count; else d.page += r.count; dailyMap.set(r.day, d); + const dest = destMap.get(r.repo) ?? { chart: 0, page: 0 }; + if (r.kind === 'chart') dest.chart += r.count; + else dest.page += r.count; + destMap.set(r.repo, dest); } // Fill in zero-days so the sparkline has a continuous baseline. @@ -650,6 +659,10 @@ export async function viewsSummary( const refSorted = toSorted(refs).slice(0, 8).map((x) => ({ host: x.k, count: x.count })); const countrySorted = toSorted(countries).slice(0, 8).map((x) => ({ country: x.k, count: x.count })); + const byDestination = Array.from(destMap.entries()) + .map(([repo, v]) => ({ repo, chart: v.chart, page: v.page })) + .sort((a, b) => (b.chart + b.page) - (a.chart + a.page)); + return { windowDays, totalChart, @@ -660,5 +673,6 @@ export async function viewsSummary( topReferers: refSorted, topCountries: countrySorted, daily, + byDestination, }; } diff --git a/tools/star-tracker/src/pages.ts b/tools/star-tracker/src/pages.ts index 70c30d4..0afe37f 100644 --- a/tools/star-tracker/src/pages.ts +++ b/tools/star-tracker/src/pages.ts @@ -107,14 +107,6 @@ gtag('config', 'G-P2YBZ0W8HQ'); @media (prefers-color-scheme: dark) { .seg-toggle .seg-label { border-right-color: #334155; } } .chart-preview img { max-width: 100%; border-radius: 6px; display: block; border: 1px solid #e2e8f0; box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); } @media (prefers-color-scheme: dark) { .chart-preview img { border-color: #1e293b; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } } - .chart-section .chart-preview .dark { display: none; } - .chart-section #theme-dark:checked ~ .chart-preview .light { display: none; } - .chart-section #theme-dark:checked ~ .chart-preview .dark { display: block; } - .chart-preview-auto .dark { display: none; } - @media (prefers-color-scheme: dark) { - .chart-preview-auto .light { display: none; } - .chart-preview-auto .dark { display: block; } - } .repo-list { list-style: none; padding: 0; margin: .5rem 0; display: flex; flex-direction: column; gap: 4px; } .repo-list li { display: flex; align-items: center; gap: 8px; font-size: 0.9em; } .recent-list { list-style: none; padding: 0; margin: .5rem 0 1rem; display: flex; flex-direction: column; gap: 6px; } @@ -249,9 +241,9 @@ ${body} var n = currentSplit(); var style = currentStyle(); var range = currentRange(); + var theme = currentTheme(); preview.querySelectorAll('img').forEach(function (img) { - var isDark = img.classList.contains('dark'); - img.src = base + buildQuery(isDark ? 'dark' : 'light', n, style, range, 'v=' + version); + img.src = base + buildQuery(theme, n, style, range, 'v=' + version); }); } function refreshEmbed() { @@ -306,8 +298,10 @@ export function landing(user: User | null): string {

Live example

This site dogfoods the tool — here's the wavekat org's own star history, split by top repo:

- wavekat star history (light) - wavekat star history (dark) + + + wavekat star history +

How it works

    @@ -426,8 +420,12 @@ function chartBlock( embedAlt: string, showSplit: boolean, ): string { - const previewLight = `${chartSvg}?v=${totalStars}`; - const previewDark = `${chartSvg}?v=${totalStars}&theme=dark`; + // Single preview img — JS swaps `src` on theme/split/style/range toggle. + // The previous dual design fetched both + // variants on every page load (CSS only hid the off-theme one), which + // doubled chart-view counts in the analytics panel. One img halves + // that and still supports the full toggle UX as long as JS is on. + const previewSrc = `${chartSvg}?v=${totalStars}`; const label = esc(displayName ?? slug); const altText = esc(embedAlt); const copyBtn = (kind: string) => `