fix: keep workspace file controls visible while scrolling

Scroll the file list independently so view selection, refresh, creation actions and directory navigation remain visible. Keep the selected Files entry available in projects without Git.

Add component coverage and 16 Electron layout scenarios for themes, window sizes and scroll retention. Focused tests, typecheck and lint passed; the full test run timed out after 200 seconds.

Release note: Workspace file controls remain visible while scrolling, and projects without Git retain the Files entry.
This commit is contained in:
mesalogo
2026-09-30 17:33:16 +08:00
parent 80edd15438
commit ab85e72f01
7 changed files with 274 additions and 7 deletions
@@ -211,6 +211,9 @@ describe('WorkspaceFilesPanel', () => {
expect(css).toMatch(/\.workspace-git__toolbar > \.segmented-control\s*\{[^}]*width: max-content;[^}]*border: 0/)
expect(css).toMatch(/\.workspace-git__branch-trigger\s*\{[^}]*width: auto;[^}]*height: 32px;[^}]*flex: 0 1 auto;[^}]*text-align: left/)
expect(css).toMatch(/\.workspace-files__header\s*\{[^}]*justify-content: space-between/)
expect(css).toMatch(/\.workspace-files__files-view\s*\{[^}]*flex: 1;[^}]*min-height: 0;[^}]*overflow: auto/)
expect(css).toContain('.workspace-files__navigation:has(.workspace-files__list:not([hidden]) .workspace-files__files-view:not([hidden]))')
expect(css).toMatch(/\.workspace-files__header,\s*\.workspace-files__actions,\s*\.workspace-files__breadcrumbs,[^{]*\{\s*flex-shrink: 0/)
expect(css).toMatch(/\.workspace-git__branches\s*\{[^}]*width: min\(100%, 360px\);[^}]*border-radius: var\(--radius-control\)/)
expect(css).toMatch(/\.workspace-git__branches\s*\{[^}]*position: fixed;[^}]*max-height: calc\(100vh - 32px\);[^}]*overflow: auto/)
expect(css).toMatch(/\.workspace-git__branch-list\s*\{[^}]*max-height: 240px;[^}]*overflow: auto/)
@@ -535,6 +538,7 @@ describe('WorkspaceFilesPanel', () => {
)
await screen.findByText('工作区为空。')
expect(screen.getByRole('button', { name: '文件' })).toHaveAttribute('aria-pressed', 'true')
expect(onListDirectory).toHaveBeenCalledOnce()
await changeUiLocale('en-US')
@@ -705,6 +709,7 @@ describe('WorkspaceFilesPanel', () => {
expect(screen.queryByRole('status')).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Git 工作区' })).not.toBeInTheDocument()
rerender(<WorkspaceFilesPanel {...props} isRepository={false} />)
expect(screen.getByRole('button', { name: '文件' })).toHaveAttribute('aria-pressed', 'true')
expect(screen.queryByRole('button', { name: 'Git 工作区' })).not.toBeInTheDocument()
rerender(<WorkspaceFilesPanel {...props} isRepository />)
fireEvent.click(screen.getByRole('button', { name: 'Git 工作区' }))
+4 -4
View File
@@ -492,10 +492,10 @@ export function WorkspaceFilesPanel({
</section>}
<div className="workspace-files__list" hidden={showingDiff}>
<div className="workspace-files__header">
{showGit && <div className="workspace-files__view-switch"><SegmentedControl ariaLabel={t('files.view')} value={activeView} onChange={setView} options={[
<div className="workspace-files__view-switch"><SegmentedControl ariaLabel={t('files.view')} value={activeView} onChange={setView} options={[
{ value: 'files', label: t('files.filesView') },
{ value: 'changes', label: t('management.gitWorkspace') }
]} /></div>}
...(showGit ? [{ value: 'changes' as const, label: t('management.gitWorkspace') }] : [])
]} /></div>
<button ref={refreshRef} className="icon-button" type="button" disabled={refreshing} aria-label={t('sidebar.workspace.refreshAriaLabel')} title={t('sidebar.workspace.refresh')} onClick={() => void refresh()}><RefreshCw size={14} aria-hidden="true" /></button>
</div>
{activeView === 'files' && root && <div className="workspace-files__toolbar workspace-files__actions">
@@ -555,7 +555,7 @@ export function WorkspaceFilesPanel({
</div>
</WorkspaceGitTools>}
</div>
<div hidden={activeView !== 'files'}>
<div className="workspace-files__files-view" hidden={activeView !== 'files'}>
{loadingPaths.has(browsedPath) && !root ? (
<p className="assistant-sidebar__empty">
{t('files.readingWorkspace')}
+10 -2
View File
@@ -18639,13 +18639,14 @@ textarea.field-control {
.workspace-git__history-dock > [role="separator"] { height: var(--space-2); flex: 0 0 auto; cursor: ns-resize; touch-action: none; }
.workspace-git__history-scroll { flex: 1; overflow: auto; min-height: 0; }
/* Keep the history header docked even when its content is collapsed. */
.workspace-files__navigation:has(.workspace-files__list:not([hidden]) .workspace-files__files-view:not([hidden])),
.workspace-files__navigation:has(.workspace-files__list:not([hidden]) .workspace-files__git-view:not([hidden]) .workspace-git__working:not([hidden])) {
height: 100%;
min-height: 0;
padding-bottom: 0;
}
.workspace-files__navigation:has(.workspace-git) .workspace-files,
.workspace-files__navigation:has(.workspace-git) .workspace-files__list:not([hidden]),
.workspace-files__navigation .workspace-files,
.workspace-files__navigation .workspace-files__list:not([hidden]),
.workspace-files__git-view:not([hidden]),
.workspace-git,
.workspace-git__current {
@@ -18659,8 +18660,15 @@ textarea.field-control {
.workspace-git__current { gap: var(--space-2); overflow: auto; }
.workspace-git > [role="status"] { margin: 0; flex-shrink: 0; }
.workspace-files__header,
.workspace-files__actions,
.workspace-files__breadcrumbs,
.workspace-git__toolbar,
.workspace-git__history-toggle { flex-shrink: 0; }
.workspace-files__files-view {
flex: 1;
min-height: 0;
overflow: auto;
}
.workspace-git__changes {
flex: 1;
min-height: 0;