Files
fusion/packages/dashboard/app/components/command-center/__tests__/CommandCenter.mobile-chart-layout.test.ts
gsxdsm 4df5c62854 FN-8133: add local codebase context metrics
Expose local codebase token estimates and apparent disk size in the project dashboard.

- Add a bounded, cached, symlink-safe codebase metrics service and project API.
- Display token and disk indicators across Command Center overview states.
- Add formatting, route, UI, and metric calibration coverage with a minor changeset.

Files changed:
 .changeset/fn-8133-codebase-metrics.md             |   7 +
 docs/dashboard-guide.md                            |   2 +-
 .../dashboard/app/__tests__/api-projects.test.ts   |  12 ++
 packages/dashboard/app/api/legacy.ts               |  14 ++
 .../components/command-center/CommandCenter.css    |   5 +
 .../components/command-center/CommandCenter.tsx    |  65 +++++++--
 .../CommandCenter.mobile-chart-layout.test.ts      |   4 +
 .../__tests__/CommandCenter.test.tsx               |  10 ++
 .../__tests__/SystemStatsArea.test.tsx             |   8 +
 .../command-center/areas/SystemStatsArea.tsx       |   8 +-
 .../app/utils/__tests__/formatBytes.test.ts        |  18 +++
 packages/dashboard/app/utils/formatBytes.ts        |  11 ++
 .../src/lib/__tests__/codebase-metrics.test.ts     |  62 ++++++++
 .../lib/__tests__/fixtures/token-corpus/README.md  |  17 +++
 .../fixtures/token-corpus/calibration/component.ts |   1 +
 .../fixtures/token-corpus/calibration/config.json  |   1 +
 .../lib/__tests__/fixtures/token-corpus/example.ts |   1 +
 .../lib/__tests__/fixtures/token-corpus/notes.md   |   3 +
 .../fixtures/token-corpus/reference-counts.json    |   9 ++
 .../__tests__/fixtures/token-corpus/settings.json  |   1 +
 .../lib/__tests__/fixtures/token-corpus/view.tsx   |   1 +
 packages/dashboard/src/lib/codebase-metrics.ts     | 161 +++++++++++++++++++++
 .../__tests__/codebase-metrics-route.test.ts       |  34 +++++
 .../src/routes/register-project-routes.ts          |  19 +++
 24 files changed, 456 insertions(+), 18 deletions(-)

Fusion-Task-Id: FN-8133

Fusion-Task-Lineage: d591636b-d4b8-469e-8788-c4516eeb8405

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 14:08:43 -07:00

84 lines
5.4 KiB
TypeScript

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
function readCommandCenterCss(): string {
return [
readFileSync(join(__dirname, "..", "CommandCenter.css"), "utf-8"),
readFileSync(join(__dirname, "..", "charts", "charts.css"), "utf-8"),
readFileSync(join(__dirname, "..", "areas", "areas.css"), "utf-8"),
readFileSync(join(__dirname, "..", "areas", "SystemStatsArea.css"), "utf-8"),
].join("\n");
}
function extractMobileMediaBlocks(content: string): string {
const blocks: string[] = [];
const regex = /@media[^{}]*\(max-width:\s*768px\)[^{}]*\{/g;
let match: RegExpExecArray | null;
while ((match = regex.exec(content)) !== null) {
const startIdx = match.index + match[0].length;
let braceCount = 1;
let endIdx = startIdx;
while (braceCount > 0 && endIdx < content.length) {
if (content[endIdx] === "{") braceCount++;
if (content[endIdx] === "}") braceCount--;
endIdx++;
}
if (braceCount === 0) blocks.push(content.slice(startIdx, endIdx - 1));
}
return blocks.join("\n");
}
describe("CommandCenter.mobile-chart-layout.css", () => {
const cssContent = readCommandCenterCss();
const mobileCss = extractMobileMediaBlocks(cssContent);
it("reads the co-located Command Center styles instead of the top-level css fixture", () => {
expect(cssContent).toContain("FN-6680");
expect(cssContent).toContain(".cc-token-series-axis");
expect(cssContent).toContain(".cc-system-chart-grid");
});
it("stacks Overview stat cards without mobile overflow", () => {
expect(mobileCss).toMatch(/\.cc-stat-grid\s*\{[^}]*min-inline-size:\s*0;[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\)/);
});
it("keeps chart primitives and text-bearing children shrink-bounded for real mobile layout", () => {
expect(cssContent).toMatch(/\.cc-token-series-axis,[\s\S]*\.cc-funnel-stage\s*\{[\s\S]*min-inline-size:\s*0;[\s\S]*max-inline-size:\s*100%/);
expect(cssContent).toMatch(/\.cc-bar-label\s*\{[^}]*min-inline-size:\s*0;[^}]*overflow:\s*hidden;[^}]*text-overflow:\s*ellipsis/);
expect(cssContent).toMatch(/\.cc-bar-track\s*\{[^}]*min-inline-size:\s*0/);
expect(cssContent).toMatch(/\.cc-bar-value\s*\{[^}]*min-inline-size:\s*0;[^}]*overflow-wrap:\s*anywhere/);
});
it("pins the corrected mobile bar row template without a competing scroll owner", () => {
expect(mobileCss).toMatch(/\.cc-bar-row\s*\{[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\)\s+minmax\(calc\(var\(--space-2xl\)\s*\+\s*var\(--space-lg\)\),\s*2fr\);[^}]*align-items:\s*start/);
expect(mobileCss).toMatch(/\.cc-bar-value\s*\{[^}]*grid-column:\s*1\s*\/\s*-1;[^}]*max-inline-size:\s*100%/);
expect(cssContent).not.toMatch(/\.cc-(?:bar|line|radial|sparkline|token-series|funnel)[^{]*\{[^}]*overflow-y:\s*(?:auto|scroll)/);
});
it("keeps line, token-series, radial, team, and system charts non-collapsing at mobile", () => {
expect(mobileCss).toMatch(/\.cc-line-chart\s*\{[^}]*block-size:\s*clamp\(calc\(var\(--space-2xl\)\s*\*\s*2\),\s*44vw,\s*calc\(var\(--space-2xl\)\s*\*\s*4\)\);[^}]*aspect-ratio:\s*auto/);
expect(mobileCss).toMatch(/\.cc-token-series-plot\s*\{[^}]*block-size:\s*clamp\(calc\(var\(--space-2xl\)\s*\+\s*var\(--space-xl\)\),\s*38vw,\s*calc\(var\(--space-2xl\)\s*\*\s*4\)\)/);
expect(mobileCss).toMatch(/\.cc-radial-gauge-ring\s*\{[^}]*inline-size:\s*clamp\(calc\(var\(--space-2xl\)\s*\*\s*2\s*\+\s*var\(--space-lg\)\),\s*44vw,\s*calc\(var\(--space-2xl\)\s*\*\s*4\)\)/);
expect(mobileCss).toMatch(/\.cc-team-chart-grid\s*\{[^}]*min-inline-size:\s*0;[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\)/);
expect(mobileCss).toMatch(/\.cc-system-chart-grid\s*\{[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\)/);
});
it("keeps recharts ResponsiveContainer parents sized without becoming scroll owners", () => {
expect(cssContent).toMatch(/\.cc-recharts-chart,[\s\S]*\.cc-recharts-empty\s*\{[\s\S]*inline-size:\s*100%;[\s\S]*block-size:\s*calc\(var\(--space-2xl\)\s*\*\s*7\s*\+\s*var\(--space-lg\)\);[\s\S]*min-inline-size:\s*0/);
expect(cssContent).toMatch(/\.cc-overview-chart-card \.cc-recharts-chart,[\s\S]*\.cc-overview-chart-card \.cc-recharts-empty\s*\{[\s\S]*inline-size:\s*100%;[\s\S]*block-size:\s*calc\(var\(--space-2xl\)\s*\*\s*7\s*\+\s*var\(--space-lg\)\);[\s\S]*min-inline-size:\s*0/);
expect(cssContent).toMatch(/\.cc-area \.cc-recharts-chart,[\s\S]*\.cc-area \.cc-recharts-empty\s*\{[\s\S]*inline-size:\s*100%;[\s\S]*block-size:\s*calc\(var\(--space-2xl\)\s*\*\s*7\s*\+\s*var\(--space-lg\)\);[\s\S]*min-inline-size:\s*0/);
expect(cssContent).not.toMatch(/\.cc-recharts-(?:chart|empty)[^{]*\{[^}]*overflow-y:\s*(?:auto|scroll)/);
});
it("normalizes chart/card/table border rhythm with design tokens only", () => {
expect(cssContent).toMatch(/\.cc-stat-card\s*\{[^}]*padding:\s*var\(--space-md\);[^}]*border:\s*1px\s+solid\s+var\(--border-subtle\);[^}]*border-radius:\s*var\(--radius-md\);[^}]*background:\s*var\(--surface-1\)/);
expect(cssContent).toMatch(/\.cc-table-wrap\s*\{[^}]*border:\s*1px\s+solid\s+var\(--border-subtle\);[^}]*border-radius:\s*var\(--radius-md\);[^}]*background:\s*var\(--surface-1\);[^}]*overflow-x:\s*auto;[^}]*overflow-y:\s*hidden/);
expect(cssContent).toMatch(/\.cc-system-vitest-card\s*\{[^}]*border:\s*1px\s+solid\s+var\(--border-subtle\);[^}]*border-radius:\s*var\(--radius-md\);[^}]*background:\s*var\(--surface-1\)/);
});
});