Skip to content

Commit 99dbc9a

Browse files
fix(files): root file browser at repo directory instead of display name (#339)
1 parent 1d477bd commit 99dbc9a

5 files changed

Lines changed: 44 additions & 26 deletions

File tree

‎frontend/src/components/file-browser/FileBrowserSheet.tsx‎

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useState, memo, useCallback, useRef } from 'react'
22
import { FileBrowser, type FileBrowserHandle } from './FileBrowser'
3+
import { getRepoRelativeDisplayPath } from './display-path'
34
import { Button } from '@/components/ui/button'
45
import { PathDisplay } from '@/components/ui/path-display'
56
import { FullscreenSheet, FullscreenSheetHeader, FullscreenSheetContent } from '@/components/ui/fullscreen-sheet'
@@ -71,28 +72,9 @@ export const FileBrowserSheet = memo(function FileBrowserSheet({ isOpen, onClose
7172
return
7273
}
7374

74-
if (!info.currentPath || info.currentPath === '.' || info.currentPath === '') {
75-
setDisplayPath('/')
76-
setCurrentPath(info.currentPath || '.')
77-
return
78-
}
79-
80-
setCurrentPath(info.currentPath)
81-
82-
const pathParts = info.currentPath.split('/').filter(Boolean)
83-
84-
if (repoName) {
85-
const repoIndex = pathParts.findIndex(p => p === repoName || p.startsWith(repoName + '-'))
86-
if (repoIndex >= 0) {
87-
const subPath = pathParts.slice(repoIndex + 1)
88-
setDisplayPath(subPath.length > 0 ? '/' + subPath.join('/') : '/')
89-
} else {
90-
setDisplayPath('/' + pathParts.join('/'))
91-
}
92-
} else {
93-
setDisplayPath('/' + pathParts.join('/'))
94-
}
95-
}, [allowNavigateAboveBase, repoName])
75+
setCurrentPath(info.currentPath || '.')
76+
setDisplayPath(getRepoRelativeDisplayPath(info.currentPath || '.', normalizedBasePath))
77+
}, [allowNavigateAboveBase, normalizedBasePath])
9678

9779
const handleDownloadDirectory = useCallback(async (options: { includeGit?: boolean, includePaths?: string[] }) => {
9880
if (!currentPath) return
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { getRepoRelativeDisplayPath } from './display-path'
3+
4+
describe('getRepoRelativeDisplayPath', () => {
5+
it('returns root when current path equals the base path', () => {
6+
expect(getRepoRelativeDisplayPath('my-repo', 'my-repo')).toBe('/')
7+
})
8+
9+
it('returns root for the assistant directory regardless of display-name casing', () => {
10+
expect(getRepoRelativeDisplayPath('assistant', 'assistant')).toBe('/')
11+
})
12+
13+
it('strips the base path prefix for subdirectories', () => {
14+
expect(getRepoRelativeDisplayPath('assistant/.opencode/skills', 'assistant')).toBe('/.opencode/skills')
15+
expect(getRepoRelativeDisplayPath('my-repo/src', 'my-repo')).toBe('/src')
16+
})
17+
18+
it('handles worktree directories with branch suffixes', () => {
19+
expect(getRepoRelativeDisplayPath('my-repo-feature', 'my-repo-feature')).toBe('/')
20+
expect(getRepoRelativeDisplayPath('my-repo-feature/src', 'my-repo-feature')).toBe('/src')
21+
})
22+
23+
it('treats "." as the workspace root base', () => {
24+
expect(getRepoRelativeDisplayPath('.', '.')).toBe('/')
25+
expect(getRepoRelativeDisplayPath('some-repo', '.')).toBe('/some-repo')
26+
})
27+
28+
it('falls back to the full path when outside the base path', () => {
29+
expect(getRepoRelativeDisplayPath('other-repo/file', 'assistant')).toBe('/other-repo/file')
30+
})
31+
})
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function getRepoRelativeDisplayPath(currentPath: string, basePath: string): string {
2+
const baseParts = basePath === '.' ? [] : basePath.split('/').filter(Boolean)
3+
const pathParts = currentPath === '.' ? [] : currentPath.split('/').filter(Boolean)
4+
const isWithinBase = baseParts.every((part, index) => pathParts[index] === part)
5+
const subParts = isWithinBase ? pathParts.slice(baseParts.length) : pathParts
6+
return subParts.length > 0 ? '/' + subParts.join('/') : '/'
7+
}

‎frontend/src/pages/AssistantRedirect.tsx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export function AssistantRedirect() {
3737
})
3838

3939
const assistantDirectory = repo?.fullPath
40-
const assistantFileBasePath = assistantDirectory?.split('/').filter(Boolean).at(-1)
4140

4241
useSSE(opcodeUrl, assistantDirectory)
4342

@@ -82,7 +81,7 @@ export function AssistantRedirect() {
8281
</div>
8382
{assistantDirectory && (
8483
<>
85-
<FileBrowserSheet isOpen={fileBrowserOpen} onClose={() => setFileBrowserOpen(false)} basePath={assistantFileBasePath} repoName="Assistant" repoId={repoId} />
84+
<FileBrowserSheet isOpen={fileBrowserOpen} onClose={() => setFileBrowserOpen(false)} basePath={repo?.localPath} repoName="Assistant" repoId={repoId} />
8685
<RepoMcpDialog open={mcpDialogOpen} onOpenChange={setMcpDialogOpen} directory={assistantDirectory} />
8786
{assistantDirectory && opcodeUrl ? (
8887
<RepoSkillsDialog

‎frontend/src/pages/SessionDetail.tsx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,7 @@ export function SessionDetail() {
206206
}, [lastAssistantMessage, session?.time?.compacting, sessionStatus.type])
207207
const hasIncompleteMessages = lastAssistantMessage ? !('completed' in lastAssistantMessage.info.time && lastAssistantMessage.info.time.completed) : false;
208208
const isStreamingResponse = hasIncompleteMessages && isSessionActive;
209-
const assistantFileBasePath = repo?.fullPath.split('/').filter(Boolean).at(-1);
210-
const workspaceBasePath = (isAssistantSession ? assistantFileBasePath : repo?.localPath) ?? repo?.localPath;
209+
const workspaceBasePath = repo?.localPath;
211210

212211
useEffect(() => {
213212
setActivePromptFileBasePath(sessionDirectory ? workspaceBasePath ?? null : null)

0 commit comments

Comments
 (0)