diff --git a/src/renderer/src/components/sidebar/SidebarHostScopeStrip.tsx b/src/renderer/src/components/sidebar/SidebarHostScopeStrip.tsx index 9e7cfdfac83..f94fb4ee2e7 100644 --- a/src/renderer/src/components/sidebar/SidebarHostScopeStrip.tsx +++ b/src/renderer/src/components/sidebar/SidebarHostScopeStrip.tsx @@ -1,17 +1,9 @@ import React from 'react' -import { AlertTriangle, ChevronDown, Circle, Loader2, Monitor, Server } from 'lucide-react' +import { AlertTriangle, Loader2, X } from 'lucide-react' import { useAppStore } from '@/store' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { ALL_EXECUTION_HOSTS_SCOPE } from '../../../../shared/execution-host' import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuTrigger -} from '@/components/ui/dropdown-menu' -import { - getSidebarHostHealthLabel, getSidebarHostScopeLabel, shouldShowHostScopeControls, type SidebarHostScopeOption @@ -19,104 +11,61 @@ import { import { useSidebarHostScopeOptions } from './use-sidebar-host-scope-options' import { translate } from '@/i18n/i18n' -function HostHealthDot({ health }: { health: SidebarHostScopeOption['health'] }) { +function HostScopeWarningIcon({ health }: { health: SidebarHostScopeOption['health'] }) { + // Why: the banner stays quiet unless the scoped host needs attention. if (health === 'connecting') { return } if (health === 'blocked' || health === 'error') { return } - return ( - - ) + return null } +/** Shown only when the sidebar is scoped to a single host: names the scope and + * offers the way back. In All-hosts view the host section headers tell the + * story, so no persistent strip renders; scope switching lives in the + * workspace options menu. */ const SidebarHostScopeStrip = React.memo(function SidebarHostScopeStrip() { const workspaceHostScope = useAppStore((s) => s.workspaceHostScope) const setWorkspaceHostScope = useAppStore((s) => s.setWorkspaceHostScope) const { hostOptions, hostScopeOptions } = useSidebarHostScopeOptions() + if (workspaceHostScope === ALL_EXECUTION_HOSTS_SCOPE) { + return null + } if (!shouldShowHostScopeControls(hostOptions)) { return null } const label = getSidebarHostScopeLabel(workspaceHostScope, hostScopeOptions) - const selectedHost = hostOptions.find((host) => host.id === workspaceHostScope) const selectedScope = hostScopeOptions.find((option) => option.id === workspaceHostScope) - // Why: saved-but-idle hosts inflate a raw registry count; connection state - // is what users act on (per the design mock's "3 connected"). - const connectedCount = hostOptions.filter( - (host) => host.health === 'local' || host.health === 'available' - ).length - const meta = - workspaceHostScope === 'all' - ? translate( - 'auto.components.sidebar.SidebarHostScopeStrip.connectedCount', - '{{value0}} connected', - { - value0: connectedCount - } - ) - : (selectedHost?.detail ?? 'Host') return (
- - - - - - setWorkspaceHostScope(value as typeof workspaceHostScope)} - > - {hostScopeOptions.map((option) => ( - - - - {option.label} - - {option.detail && ( - - {getSidebarHostHealthLabel(option.health)} · {option.detail} - - )} - - ))} - - - +
+ + + + {translate( + 'auto.components.sidebar.SidebarHostScopeStrip.scopedTo', + '{{value0}} only', + { + value0: label + } + )} + + + +
) }) diff --git a/src/renderer/src/components/sidebar/WorktreeList.tsx b/src/renderer/src/components/sidebar/WorktreeList.tsx index 2259014c548..27222698b74 100644 --- a/src/renderer/src/components/sidebar/WorktreeList.tsx +++ b/src/renderer/src/components/sidebar/WorktreeList.tsx @@ -15,7 +15,6 @@ import { FolderInput, FolderPlus, Plus, - Server, Shapes, SlidersHorizontal, Trash2 @@ -473,26 +472,26 @@ function SectionMetricsBadge({ count }: { count: number }): React.JSX.Element { ) } -function HostHeaderHealthIcon({ health }: { health: HostHeaderRow['health'] }): React.JSX.Element { +function HostHeaderHealthIcon({ + health +}: { + health: HostHeaderRow['health'] +}): React.JSX.Element | null { + // Why: healthy is the default state — indicating it adds noise. Only states + // needing attention (connecting, blocked, error, disconnected) get a mark. if (health === 'connecting') { - return + return } if (health === 'blocked' || health === 'error') { - return + return } - return ( - - ) + if (health === 'disconnected') { + return + } + return null } -function getHostHeaderDetail(row: HostHeaderRow): { text: string; isWarning: boolean } { +function getHostHeaderDetail(row: HostHeaderRow): { text: string; isWarning: boolean } | null { // Why: a blocked compatibility verdict gets a compact warning treatment so one // skewed host stands out without altering how its siblings render. if (row.health === 'blocked') { @@ -512,7 +511,12 @@ function getHostHeaderDetail(row: HostHeaderRow): { text: string; isWarning: boo isWarning: true } } - return { text: row.detail, isWarning: false } + // Why: the transport suffix only earns space on remote hosts; "This + // computer" on Local Mac is noise. + if (row.kind !== 'local') { + return { text: row.detail, isWarning: false } + } + return null } function HostSectionHeader({ @@ -525,14 +529,14 @@ function HostSectionHeader({ const detail = getHostHeaderDetail(row) return (
- {/* Why: host headers are group labels, not cards — they must read as a - lighter tier than the host scope strip above, mirroring the other - sidebar group headers (clickable row, hover chevron, same type). */} + {/* Why: host headers are quiet group labels, one tier below workspace + cards — uppercase muted type, no icon, status mark only when the + host needs attention (matches the design doc's sidebar mock). */}
{ if (e.key === 'Enter' || e.key === ' ') { @@ -541,18 +545,21 @@ function HostSectionHeader({ } }} > - -
-
{row.label}
- - {detail.text} - +
+
+ {row.label} +
+ {detail ? ( + + · {detail.text} + + ) : null}
diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 98677104f3f..4ee88577c6e 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -3344,7 +3344,8 @@ "3e102f111c": "All hosts" }, "SidebarHostScopeStrip": { - "connectedCount": "{{value0}} connected" + "scopedTo": "{{value0}} only", + "backToAll": "All hosts" }, "SidebarWorkspaceOptionsMenu": { "95c9754653": "Agent activity layout", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index e3bd82637bb..5c7edb0b8ad 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -3324,7 +3324,8 @@ "82594419ba": "フィルター" }, "SidebarHostScopeStrip": { - "connectedCount": "{{value0}} connected" + "scopedTo": "{{value0}} only", + "backToAll": "All hosts" }, "SidebarWorkspaceOptionsMenu": { "95c9754653": "エージェントアクティビティのレイアウト", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 9393d811116..2f400fdb03b 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -3324,7 +3324,8 @@ "82594419ba": "필터" }, "SidebarHostScopeStrip": { - "connectedCount": "{{value0}} connected" + "scopedTo": "{{value0}} only", + "backToAll": "All hosts" }, "SidebarWorkspaceOptionsMenu": { "95c9754653": "에이전트 활동 레이아웃", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 640c7e348b7..d87a6b3648b 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -3324,7 +3324,8 @@ "82594419ba": "过滤器" }, "SidebarHostScopeStrip": { - "connectedCount": "{{value0}} connected" + "scopedTo": "{{value0}} only", + "backToAll": "All hosts" }, "SidebarWorkspaceOptionsMenu": { "95c9754653": "代理活动布局",