Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions src/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,16 +247,19 @@ 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-/, '')
const _home = homedir()
const _homePrefix = _home.endsWith('/') ? _home : _home + '/'

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('/')
}
Expand All @@ -282,7 +285,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>
Expand Down Expand Up @@ -442,7 +445,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)

Expand All @@ -460,7 +463,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} />
Expand Down
35 changes: 34 additions & 1 deletion tests/dashboard.test.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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', [])
Expand Down
Loading