Skip to content

Commit 1b52878

Browse files
Unify workbench chrome controls
Move project, branch, run, update, notification, and account controls into the title bar using the shared chrome sizing contract. Make Search a normal Activity navigation item and restore macOS traffic-light alignment.
1 parent d55c8cc commit 1b52878

21 files changed

Lines changed: 296 additions & 286 deletions

src/features/git/components/git-branch-manager.tsx

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
CommandTabs,
2020
useCommandListNavigation,
2121
} from "@/ui/command";
22-
import { GitBranchIcon, FolderOpenIcon, NodesIcon } from "@/ui/icons";
22+
import { ChevronExpandYIcon, GitBranchIcon, FolderOpenIcon, NodesIcon } from "@/ui/icons";
2323
import { showConfirmDialog } from "@/ui/dialog";
2424
import { cn } from "@/utils/cn";
2525
import { getFolderName, getRelativePath } from "@/utils/path-helpers";
@@ -42,6 +42,7 @@ interface GitBranchManagerProps {
4242
onRepositoryChange?: (repoPath: string | null) => void;
4343
paletteTarget?: boolean;
4444
openEventName?: string;
45+
triggerMode?: "repository" | "branch";
4546
}
4647

4748
type GitBranchManagerTab = "branches" | "worktrees" | "repositories";
@@ -130,6 +131,7 @@ const GitBranchManager = ({
130131
onRepositoryChange,
131132
paletteTarget = false,
132133
openEventName = "athas:open-branch-manager",
134+
triggerMode = "repository",
133135
}: GitBranchManagerProps) => {
134136
const [branches, setBranches] = useState<string[]>([]);
135137
const [worktrees, setWorktrees] = useState<GitWorktree[]>([]);
@@ -442,6 +444,10 @@ const GitBranchManager = ({
442444
if (!repoPath || isDropdownOpen) return;
443445
setActiveTab("branches");
444446
setIsDropdownOpen(true);
447+
if (triggerMode === "branch") {
448+
await loadBranches();
449+
return;
450+
}
445451
await Promise.all([loadBranches(), loadWorktrees()]);
446452
};
447453

@@ -551,22 +557,37 @@ const GitBranchManager = ({
551557
onClick={() => void handleOpenDropdown()}
552558
disabled={isLoading}
553559
variant="ghost"
560+
size={triggerMode === "branch" ? "chrome" : "default"}
554561
className={cn(
555562
"w-fit max-w-full min-w-0 shrink justify-start overflow-hidden text-left hover:bg-accent/80",
563+
triggerMode === "branch" && "max-w-48",
556564
isDropdownOpen && "bg-accent/80",
557565
)}
558566
title={selectorRepoPath ?? undefined}
559-
aria-label={`Switch repository or branch. ${activeRepositoryLabel}, branch ${currentBranch}`}
567+
aria-label={
568+
triggerMode === "branch"
569+
? `Switch branch. Current branch: ${currentBranch}`
570+
: `Switch repository or branch. ${activeRepositoryLabel}, branch ${currentBranch}`
571+
}
560572
>
561-
<FolderOpenIcon />
562-
<span className="flex min-w-0 flex-1 flex-col items-start overflow-hidden leading-none">
563-
<span className="max-w-full truncate font-medium text-foreground ui-text-sm">
564-
{activeRepositoryLabel}
565-
</span>
566-
<span className="max-w-full truncate font-normal text-subtle-foreground ui-text-caption">
567-
{currentBranch}
568-
</span>
569-
</span>
573+
{triggerMode === "branch" ? (
574+
<>
575+
<span className="min-w-0 truncate">{currentBranch}</span>
576+
<ChevronExpandYIcon className="text-subtle-foreground" />
577+
</>
578+
) : (
579+
<>
580+
<FolderOpenIcon />
581+
<span className="flex min-w-0 flex-1 flex-col items-start overflow-hidden leading-none">
582+
<span className="max-w-full truncate font-medium text-foreground ui-text-sm">
583+
{activeRepositoryLabel}
584+
</span>
585+
<span className="max-w-full truncate font-normal text-subtle-foreground ui-text-caption">
586+
{currentBranch}
587+
</span>
588+
</span>
589+
</>
590+
)}
570591
</Button>
571592

572593
<GitCommandSurface
@@ -592,7 +613,11 @@ const GitBranchManager = ({
592613
availableRepoPaths.length === 1 ? "y" : "ies"
593614
}`
594615
}
595-
headerAddon={<CommandTabs items={tabItems} ariaLabel="Git selector sections" />}
616+
headerAddon={
617+
triggerMode === "repository" ? (
618+
<CommandTabs items={tabItems} ariaLabel="Git selector sections" />
619+
) : undefined
620+
}
596621
>
597622
<CommandList>
598623
{activeTab === "branches" && !createBranchName && filteredBranches.length === 0 ? (

src/features/layout/components/app-update-control.tsx

Lines changed: 18 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
import { useMemo, useRef, useState } from "react";
22
import { useAutoUpdate } from "@/features/settings/hooks/use-auto-update";
3+
import { Button } from "@/ui/button";
34
import { Dropdown } from "@/ui/dropdown";
45
import { Spinner } from "@/ui/spinner";
56
import { CalendarIcon, ClockIcon, DownloadIcon, FileTextIcon, XCircleIcon } from "@/ui/icons";
6-
import { SidebarIconButton, SidebarListItem } from "@/ui/sidebar";
77
import Tooltip from "@/ui/tooltip";
88

9-
interface AppUpdateControlProps {
10-
expanded: boolean;
11-
}
12-
13-
export function AppUpdateControl({ expanded }: AppUpdateControlProps) {
9+
export function AppUpdateControl() {
1410
const {
1511
showUpdateIndicator,
1612
downloading,
@@ -79,13 +75,6 @@ export function AppUpdateControl({ expanded }: AppUpdateControlProps) {
7975

8076
if (!showUpdateIndicator || !updateInfo) return null;
8177

82-
const updateLabel = downloading
83-
? `${downloadProgress?.percentage ?? 0}%`
84-
: installing
85-
? "Installing"
86-
: updateError
87-
? "Update failed"
88-
: "Update available";
8978
const updateTooltip = updateError
9079
? updateError
9180
: downloading
@@ -95,49 +84,33 @@ export function AppUpdateControl({ expanded }: AppUpdateControlProps) {
9584
: `Update available: ${updateInfo.version}`;
9685

9786
return (
98-
<div ref={updateMenuRef} className="w-full">
99-
{expanded ? (
100-
<SidebarListItem
101-
leading={
102-
updateBusy ? (
103-
<Spinner label={downloading ? "Downloading" : "Installing"} compact />
104-
) : (
105-
<DownloadIcon />
106-
)
107-
}
87+
<div ref={updateMenuRef}>
88+
<Tooltip content={updateTooltip} side="bottom">
89+
<Button
90+
type="button"
91+
variant="ghost"
92+
iconOnly
93+
size="chrome"
10894
active={isUpdateMenuOpen}
10995
disabled={updateBusy}
11096
onClick={() => setIsUpdateMenuOpen((open) => !open)}
11197
aria-haspopup="menu"
11298
aria-expanded={isUpdateMenuOpen}
11399
aria-label={updateTooltip}
114100
>
115-
{updateLabel}
116-
</SidebarListItem>
117-
) : (
118-
<Tooltip content={updateTooltip} side="right">
119-
<SidebarIconButton
120-
active={isUpdateMenuOpen}
121-
disabled={updateBusy}
122-
onClick={() => setIsUpdateMenuOpen((open) => !open)}
123-
aria-haspopup="menu"
124-
aria-expanded={isUpdateMenuOpen}
125-
aria-label={updateTooltip}
126-
>
127-
{updateBusy ? (
128-
<Spinner label={downloading ? "Downloading" : "Installing"} compact />
129-
) : (
130-
<DownloadIcon />
131-
)}
132-
</SidebarIconButton>
133-
</Tooltip>
134-
)}
101+
{updateBusy ? (
102+
<Spinner label={downloading ? "Downloading" : "Installing"} compact />
103+
) : (
104+
<DownloadIcon />
105+
)}
106+
</Button>
107+
</Tooltip>
135108
<Dropdown
136109
isOpen={isUpdateMenuOpen}
137110
onClose={() => setIsUpdateMenuOpen(false)}
138111
anchorRef={updateMenuRef}
139-
anchorSide="top"
140-
anchorAlign="start"
112+
anchorSide="bottom"
113+
anchorAlign="end"
141114
items={updateMenuItems}
142115
className="min-w-52"
143116
/>

src/features/layout/components/sidebar/activity-project-switcher.tsx renamed to src/features/layout/components/project-switcher.tsx

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,29 @@ import { findBestProjectIcon } from "@/features/window/utils/project-icons";
1515
import { Button } from "@/ui/button";
1616
import {
1717
DropdownMenu,
18+
DropdownMenuContent,
1819
DropdownMenuItem,
1920
DropdownMenuRadioGroup,
2021
DropdownMenuRadioItem,
2122
DropdownMenuSeparator,
2223
DropdownMenuTrigger,
2324
} from "@/ui/dropdown";
2425
import { ChevronExpandYIcon, FolderOpenIcon, ImageIcon, RemoteIcon, XIcon } from "@/ui/icons";
25-
import { SidebarIconButton, SidebarListItem, SidebarMenuContent } from "@/ui/sidebar";
2626
import { showConfirmDialog } from "@/ui/dialog";
2727
import { toast } from "sonner";
28-
import { getClosedRemoteConnections, getProjectRemoteConnectionId } from "./project-switcher-items";
29-
import { getProjectNameFromPath, isRemoteProjectPath, ProjectGlyph } from "./project-glyph";
28+
import {
29+
getClosedRemoteConnections,
30+
getProjectRemoteConnectionId,
31+
} from "./sidebar/project-switcher-items";
32+
import { getProjectNameFromPath, isRemoteProjectPath, ProjectGlyph } from "./sidebar/project-glyph";
3033

31-
export function ActivityProjectSwitcher({
32-
expanded,
34+
export function ProjectSwitcher({
3335
project,
3436
projects,
3537
isSwitchingProject,
3638
onSelectProject,
3739
onAddRemote,
3840
}: {
39-
expanded: boolean;
4041
project?: ProjectTab;
4142
projects: ProjectTab[];
4243
isSwitchingProject: boolean;
@@ -83,18 +84,14 @@ export function ActivityProjectSwitcher({
8384
let cancelled = false;
8485

8586
findBestProjectIcon(projectPath).then((iconFile) => {
86-
if (!cancelled) {
87-
setDetectedIconPath(iconFile?.path);
88-
}
87+
if (!cancelled) setDetectedIconPath(iconFile?.path);
8988
});
9089

9190
return () => {
9291
cancelled = true;
9392
};
9493
}, [displayProject, displayProjectKey, customIcon, isRemote, projectPath]);
9594

96-
const projectGlyph = <ProjectGlyph projectPath={projectPath} iconPath={displayIconPath} />;
97-
9895
const handleConnectRemote = async (connectionId: string, providedPassword?: string) => {
9996
const connection = remoteConnections.find((candidate) => candidate.id === connectionId);
10097
if (!connection || connectingRemoteId === connectionId) return;
@@ -181,23 +178,20 @@ export function ActivityProjectSwitcher({
181178
>
182179
<DropdownMenuTrigger
183180
render={
184-
expanded ? (
185-
<SidebarListItem
186-
leading={projectGlyph}
187-
trailing={<ChevronExpandYIcon />}
188-
width="content"
189-
aria-label="Switch project"
190-
>
191-
{projectName}
192-
</SidebarListItem>
193-
) : (
194-
<SidebarIconButton aria-label="Switch project" title={projectName}>
195-
{projectGlyph}
196-
</SidebarIconButton>
197-
)
181+
<Button
182+
variant="ghost"
183+
size="chrome"
184+
className="min-w-0 max-w-48 shrink"
185+
aria-label={`Switch project. Current project: ${projectName}`}
186+
title={projectPath || projectName}
187+
>
188+
<ProjectGlyph projectPath={projectPath} iconPath={displayIconPath} />
189+
<span className="min-w-0 truncate">{projectName}</span>
190+
<ChevronExpandYIcon className="text-subtle-foreground" />
191+
</Button>
198192
}
199193
/>
200-
<SidebarMenuContent>
194+
<DropdownMenuContent side="bottom" align="start">
201195
{projects.length > 0 || closedRemoteConnections.length > 0 ? (
202196
<>
203197
<DropdownMenuRadioGroup
@@ -268,7 +262,7 @@ export function ActivityProjectSwitcher({
268262
<RemoteIcon />
269263
Add remote…
270264
</DropdownMenuItem>
271-
</SidebarMenuContent>
265+
</DropdownMenuContent>
272266
</DropdownMenu>
273267
{iconPickerProject ? (
274268
<ProjectIconPicker

src/features/layout/components/sidebar/activity-bar-menu.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ interface ActivityBarMenuProps {
2525
navigationItems: ActivityNavigationItem[];
2626
hiddenNavigationItemIds: string[];
2727
coreFeatures: CoreFeaturesState;
28-
showProjectSwitcher: boolean;
2928
showAgentHistory: boolean;
3029
showTerminals: boolean;
3130
showProjectDots: boolean;
@@ -37,7 +36,6 @@ interface ActivityBarMenuProps {
3736
onSearch: () => void;
3837
onOpenExtensions: () => void;
3938
onNavigationItemVisibleChange: (itemId: string, visible: boolean) => void;
40-
onProjectSwitcherVisibleChange: (visible: boolean) => void;
4139
onAgentHistoryVisibleChange: (visible: boolean) => void;
4240
onTerminalsVisibleChange: (visible: boolean) => void;
4341
onProjectDotsVisibleChange: (visible: boolean) => void;
@@ -48,7 +46,6 @@ export function ActivityBarMenu({
4846
navigationItems,
4947
hiddenNavigationItemIds,
5048
coreFeatures,
51-
showProjectSwitcher,
5249
showAgentHistory,
5350
showTerminals,
5451
showProjectDots,
@@ -60,7 +57,6 @@ export function ActivityBarMenu({
6057
onSearch,
6158
onOpenExtensions,
6259
onNavigationItemVisibleChange,
63-
onProjectSwitcherVisibleChange,
6460
onAgentHistoryVisibleChange,
6561
onTerminalsVisibleChange,
6662
onProjectDotsVisibleChange,
@@ -106,13 +102,6 @@ export function ActivityBarMenu({
106102
</ContextMenuSubTrigger>
107103
<ContextMenuSubContent>
108104
<ContextMenuGroup>
109-
<ContextMenuCheckboxItem
110-
checked={showProjectSwitcher}
111-
onCheckedChange={onProjectSwitcherVisibleChange}
112-
>
113-
<FolderIcon />
114-
Project Switcher
115-
</ContextMenuCheckboxItem>
116105
{navigationItems.map((item) => (
117106
<ContextMenuCheckboxItem
118107
key={item.id}

0 commit comments

Comments
 (0)