From ca98f0285fc50ba46cf25e3e2b7a36adb4e944ca Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Thu, 10 Sep 2026 20:06:01 -0500 Subject: [PATCH] fix(control-center): show full line breakdown on Projects tab KPI row Replace the 4-card KPI row (repositories/code lines/largest repo/ categories) with the same 6-metric breakdown the internal static ecosystem report already shows: files, documentation, code lines, comment lines, blank lines, total lines -- code lines stays the primary/highlighted metric and still drives ranking (donut, bars, largest-repo ordering, default sort), matching the earlier decision that code-only LOC is the more style-independent metric; the other lines are shown transparently alongside it instead of hidden. "documentation" uses the same narrow definition the internal report uses (Markdown-only, not every docs-type language) so the number matches across both surfaces. Fixes a test fixture gap surfaced by this change: PublicEcosystemPage's mock ReportData.grand was missing `files`, unnoticed until the tab started reading it directly. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_014sNhqTgqynXCnKpmt7KbgK --- .../cc-ui/src/pages/EcosystemReportTabs.tsx | 53 ++++++++++--------- .../src/pages/PublicEcosystemPage.test.tsx | 2 +- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/frontend/cc-ui/src/pages/EcosystemReportTabs.tsx b/frontend/cc-ui/src/pages/EcosystemReportTabs.tsx index 6204dfd6..e0bfe3a4 100644 --- a/frontend/cc-ui/src/pages/EcosystemReportTabs.tsx +++ b/frontend/cc-ui/src/pages/EcosystemReportTabs.tsx @@ -64,7 +64,6 @@ export const LANG: Record= 1000 ? (n / 1000).toFixed(0) + 'k' : String(n) } -function round1(n: number) { return Math.round(n * 10) / 10 } // ── Shared UI pieces ─────────────────────────────────────────────────────────── export function KpiCard({ label, value, sub, color }: { label: string; value: string | number; sub?: string; color?: string }) { @@ -209,21 +208,25 @@ export const thStyle = (active: boolean, right = false): React.CSSProperties => // ── Tab: Projects ─────────────────────────────────────────────────────────── export function ProjectsTab({ data }: { data: ReportData }) { - const { grand } = data - const totalAll = grand.code + grand.comment + grand.blank || 1 - - // Rank/size everything on this tab by total lines (code + comment + - // blank) rather than code-only -- overrides the backend's code-based - // `pct` with a total-based one so the KPI, donut, bars, and table - // share column all agree on the same denominator. + const { grand, languages } = data + const totalCode = grand.code || 1 + const totalAll = grand.code + grand.comment + grand.blank + // Same narrow definition the internal report uses: Markdown only, not + // every "docs"-type language -- keeps this number identical to the + // one on the static ecosystem report. + const docLines = languages.find(l => l.name === 'Markdown')?.code ?? 0 + + // Rank/size everything on this tab by code lines (excl. comments/blank) + // -- the standard, style-independent LOC metric -- while still + // carrying a `total` (code+comment+blank) field per row for the + // secondary "total" column in the breakdown table below. const projects = useMemo(() => data.projects .map(r => ({ ...r, total: r.code + r.comment + r.blank })) - .map(r => ({ ...r, pct: round1(100 * r.total / totalAll) })) - .sort((a, b) => b.total - a.total), - [data.projects, totalAll] + .sort((a, b) => b.code - a.code), + [data.projects] ) - const tbl = useTable(projects as unknown as Record[], 'total') + const tbl = useTable(projects as unknown as Record[], 'code') const filtered = useMemo(() => applyTable(projects as unknown as Record[], tbl, ['name', 'catLabel'], 'cat'), @@ -233,10 +236,10 @@ export function ProjectsTab({ data }: { data: ReportData }) { const paged = filtered.slice((tbl.page - 1) * tbl.perPage, tbl.page * tbl.perPage) const catTotals: Record = {} - projects.forEach(r => { catTotals[r.cat] = (catTotals[r.cat] || 0) + r.total }) + projects.forEach(r => { catTotals[r.cat] = (catTotals[r.cat] || 0) + r.code }) const catOrder = Object.keys(CAT).sort((a, b) => (catTotals[b] || 0) - (catTotals[a] || 0)) const donutData = catOrder.map(k => ({ name: CAT[k].label, value: catTotals[k] || 0, color: CAT[k].color })) - const maxTotal = projects[0]?.total || 1 + const maxCode = projects[0]?.code || 1 const SortTh = ({ col, label, right = false }: { col: string; label: string; right?: boolean }) => ( { tbl.toggleSort(col); }} style={thStyle(tbl.sortKey === col, right)}> @@ -246,37 +249,39 @@ export function ProjectsTab({ data }: { data: ReportData }) { return (
-
- - - - +
+ + + + + +
- +
- +
{catOrder.map(cat => (
{CAT[cat].label} - {((catTotals[cat] || 0) / totalAll * 100).toFixed(1)}% + {((catTotals[cat] || 0) / totalCode * 100).toFixed(1)}%
))}
{projects.slice(0, 16).map(r => { - const pct = Math.round(r.total / maxTotal * 100) + const pct = Math.round(r.code / maxCode * 100) const meta = CAT[r.cat] || CAT.infra return (
{r.name}
- {k(r.total)} + {k(r.code)}
diff --git a/frontend/cc-ui/src/pages/PublicEcosystemPage.test.tsx b/frontend/cc-ui/src/pages/PublicEcosystemPage.test.tsx index 3ec4e214..94e475d4 100644 --- a/frontend/cc-ui/src/pages/PublicEcosystemPage.test.tsx +++ b/frontend/cc-ui/src/pages/PublicEcosystemPage.test.tsx @@ -24,7 +24,7 @@ const REPORT_DATA: ReportData = { gitStatus: [ { repo: 'omnibioai-auth', branch: 'main', clean: true, nonMain: false, details: '' }, ] as any, - grand: { code: 5000, comment: 100, blank: 200 } as any, + grand: { files: 10, code: 5000, comment: 100, blank: 200 } as any, generated_at: '2026-08-14T00:00:00Z', }