mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-16 19:44:14 +00:00
Fix mangled project paths in dashboard (#320)
* 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.
* shortProject: cache homedir, normalize Windows backslashes, fix stale test helper
---------
Co-authored-by: Abdallah Meghraoui <abdallah.meghraoui@outlook.com>
This commit is contained in:
parent
38e41e93c3
commit
3b71650f24
2 changed files with 48 additions and 12 deletions
|
|
@ -248,16 +248,19 @@ function DailyActivity({ projects, days = 14, pw, bw }: { projects: ProjectSumma
|
|||
)
|
||||
}
|
||||
|
||||
const _homeEncoded = homedir().replace(/\//g, '-')
|
||||
const _home = homedir()
|
||||
const _homePrefix = _home.endsWith('/') ? _home : _home + '/'
|
||||
|
||||
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 normalized = absPath.replace(/\\/g, '/')
|
||||
let path: string
|
||||
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'
|
||||
const parts = path.split('-').filter(Boolean)
|
||||
const parts = path.split('/').filter(Boolean)
|
||||
if (parts.length <= 3) return parts.join('/')
|
||||
return parts.slice(-3).join('/')
|
||||
}
|
||||
|
|
@ -283,7 +286,7 @@ function ProjectBreakdown({ projects, pw, bw, budgets }: { projects: ProjectSumm
|
|||
return (
|
||||
<Text key={`${project.project}-${i}`} wrap="truncate-end">
|
||||
<HBar value={project.totalCostUSD} max={maxCost} width={bw} />
|
||||
<Text dimColor> {fit(shortProject(project.project), nw)}</Text>
|
||||
<Text dimColor> {fit(shortProject(project.projectPath), nw)}</Text>
|
||||
<Text color={GOLD}>{formatCost(project.totalCostUSD).padStart(8)}</Text>
|
||||
<Text color={GOLD}>{avgCost.padStart(PROJECT_COL_AVG)}</Text>
|
||||
<Text>{String(project.sessions.length).padStart(6)}</Text>
|
||||
|
|
@ -443,7 +446,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)
|
||||
|
||||
|
|
@ -461,7 +464,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 (
|
||||
<Text key={`${session.sessionId}-${i}`} wrap="truncate-end">
|
||||
<HBar value={session.totalCostUSD} max={maxCost} width={bw} />
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
@ -53,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)
|
||||
}
|
||||
|
||||
|
|
@ -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', [])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue