From 872f635a0d8b0e6a37665d79bed0781530a0cec8 Mon Sep 17 00:00:00 2001 From: Abdallah Meghraoui Date: Sun, 10 May 2026 22:15:31 +0200 Subject: [PATCH 1/2] Fix mangled project paths in By Project and Top Sessions panels shortProject() decoded Claude Code slugs by splitting on '-', which broke directory names containing dashes ('foo-bar' became 'foo/bar'). Switch the dashboard to consume ProjectSummary.projectPath (the canonical cwd already extracted by parser.ts) and rewrite shortProject to operate on a real absolute path. --- src/dashboard.tsx | 25 +++++++++++++------------ tests/dashboard.test.ts | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/dashboard.tsx b/src/dashboard.tsx index b46dbcce..fdd79928 100644 --- a/src/dashboard.tsx +++ b/src/dashboard.tsx @@ -247,16 +247,17 @@ function DailyActivity({ projects, days = 14, pw, bw }: { projects: ProjectSumma ) } -const _homeEncoded = homedir().replace(/\//g, '-') - -function shortProject(encoded: string): string { - let path = encoded.replace(/^-/, '') - if (path.startsWith(_homeEncoded.replace(/^-/, ''))) { - path = path.slice(_homeEncoded.replace(/^-/, '').length).replace(/^-/, '') - } - path = path.replace(/^private-tmp-[^-]+-[^-]+-/, '').replace(/^private-tmp-/, '').replace(/^tmp-/, '') +export function shortProject(absPath: string): string { + const home = homedir() + const homePrefix = home.endsWith('/') ? home : home + '/' + let path: string + if (absPath === home) path = '' + else if (absPath.startsWith(homePrefix)) path = absPath.slice(homePrefix.length) + else path = absPath + path = path.replace(/^\/+/, '') + path = path.replace(/^private\/tmp\/[^/]+\/[^/]+\//, '').replace(/^private\/tmp\//, '').replace(/^tmp\//, '') if (!path) return 'home' - const parts = path.split('-').filter(Boolean) + const parts = path.split('/').filter(Boolean) if (parts.length <= 3) return parts.join('/') return parts.slice(-3).join('/') } @@ -282,7 +283,7 @@ function ProjectBreakdown({ projects, pw, bw, budgets }: { projects: ProjectSumm return ( - {fit(shortProject(project.project), nw)} + {fit(shortProject(project.projectPath), nw)} {formatCost(project.totalCostUSD).padStart(8)} {avgCost.padStart(PROJECT_COL_AVG)} {String(project.sessions.length).padStart(6)} @@ -442,7 +443,7 @@ const TOP_SESSIONS_CALLS_COL = 6 function TopSessions({ projects, pw, bw }: { projects: ProjectSummary[]; pw: number; bw: number }) { const allSessions = projects.flatMap(p => - p.sessions.map(s => ({ ...s, projectName: p.project })) + p.sessions.map(s => ({ ...s, projectPath: p.projectPath })) ) const top = [...allSessions].sort((a, b) => b.totalCostUSD - a.totalCostUSD).slice(0, 5) @@ -460,7 +461,7 @@ function TopSessions({ projects, pw, bw }: { projects: ProjectSummary[]; pw: num const date = session.firstTimestamp ? session.firstTimestamp.slice(0, TOP_SESSIONS_DATE_LEN) : '----------' - const label = `${date} ${shortProject(session.projectName)}` + const label = `${date} ${shortProject(session.projectPath)}` return ( diff --git a/tests/dashboard.test.ts b/tests/dashboard.test.ts index 0d36e2ed..de45a5cd 100644 --- a/tests/dashboard.test.ts +++ b/tests/dashboard.test.ts @@ -1,5 +1,8 @@ +import { homedir } from 'os' + import { describe, it, expect } from 'vitest' +import { shortProject } from '../src/dashboard.js' import { formatCost } from '../src/format.js' import type { ProjectSummary, SessionSummary } from '../src/types.js' @@ -99,6 +102,36 @@ describe('TopSessions - top-5 selection', () => { }) }) +describe('shortProject - path shortening', () => { + const home = homedir() + + it('preserves directory names containing dashes', () => { + expect(shortProject(`${home}/work/my-project`)).toBe('work/my-project') + }) + + it('preserves directory names containing dots', () => { + expect(shortProject(`${home}/work/my.app.io`)).toBe('work/my.app.io') + }) + + it('returns "home" for the home dir itself', () => { + expect(shortProject(home)).toBe('home') + }) + + it('does not strip a sibling whose name shares the home prefix', () => { + const sibling = `${home}-backup/proj` + expect(shortProject(sibling).endsWith('proj')).toBe(true) + expect(shortProject(sibling)).not.toMatch(/^-/) + }) + + it('keeps only the last 3 segments for deeply nested paths', () => { + expect(shortProject(`${home}/a/b/c/d/e/f`)).toBe('d/e/f') + }) + + it('handles paths outside the home dir', () => { + expect(shortProject('/opt/myproject')).toBe('opt/myproject') + }) +}) + describe('avg/s in ProjectBreakdown', () => { it('returns dash for a project with no sessions', () => { const project = makeProject('proj', []) From eca60a38eee9ece9e226ce94fa4d9e2b015b7d8f Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Mon, 11 May 2026 22:02:15 -0700 Subject: [PATCH 2/2] shortProject: cache homedir, normalize Windows backslashes, fix stale test helper --- src/dashboard.tsx | 12 +++++++----- tests/dashboard.test.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/dashboard.tsx b/src/dashboard.tsx index fdd79928..6a95da43 100644 --- a/src/dashboard.tsx +++ b/src/dashboard.tsx @@ -247,13 +247,15 @@ function DailyActivity({ projects, days = 14, pw, bw }: { projects: ProjectSumma ) } +const _home = homedir() +const _homePrefix = _home.endsWith('/') ? _home : _home + '/' + export function shortProject(absPath: string): string { - const home = homedir() - const homePrefix = home.endsWith('/') ? home : home + '/' + const normalized = absPath.replace(/\\/g, '/') let path: string - if (absPath === home) path = '' - else if (absPath.startsWith(homePrefix)) path = absPath.slice(homePrefix.length) - else path = absPath + if (normalized === _home) path = '' + else if (normalized.startsWith(_homePrefix)) path = normalized.slice(_homePrefix.length) + else path = normalized path = path.replace(/^\/+/, '') path = path.replace(/^private\/tmp\/[^/]+\/[^/]+\//, '').replace(/^private\/tmp\//, '').replace(/^tmp\//, '') if (!path) return 'home' diff --git a/tests/dashboard.test.ts b/tests/dashboard.test.ts index de45a5cd..da802f11 100644 --- a/tests/dashboard.test.ts +++ b/tests/dashboard.test.ts @@ -56,7 +56,7 @@ function makeProject(name: string, sessions: SessionSummary[]): ProjectSummary { // Logic replicated from TopSessions component function getTopSessions(projects: ProjectSummary[], n = 5) { - const all = projects.flatMap(p => p.sessions.map(s => ({ ...s, projectName: p.project }))) + const all = projects.flatMap(p => p.sessions.map(s => ({ ...s, projectPath: p.projectPath }))) return [...all].sort((a, b) => b.totalCostUSD - a.totalCostUSD).slice(0, n) }