Skip to content

Commit 8860056

Browse files
authored
🤖 refactor: redesign workspace preview icons (#843)
- Remove status dot (status shown below via WorkspaceStatusIndicator) - Add local workspace badge (folder icon) - Gray icons when workspace is idle - Blue pulsing icons when agent is working _Generated with `mux`_
1 parent e1117fc commit 8860056

File tree

5 files changed

+62
-36
lines changed

5 files changed

+62
-36
lines changed

src/browser/components/GitStatusIndicator.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ interface GitStatusIndicatorProps {
77
gitStatus: GitStatus | null;
88
workspaceId: string;
99
tooltipPosition?: "right" | "bottom";
10+
/** When true, shows blue pulsing styling to indicate agent is working */
11+
isWorking?: boolean;
1012
}
1113

1214
/**
@@ -18,6 +20,7 @@ export const GitStatusIndicator: React.FC<GitStatusIndicatorProps> = ({
1820
gitStatus,
1921
workspaceId,
2022
tooltipPosition = "right",
23+
isWorking = false,
2124
}) => {
2225
const [showTooltip, setShowTooltip] = useState(false);
2326
const [tooltipCoords, setTooltipCoords] = useState<{ top: number; left: number }>({
@@ -118,6 +121,7 @@ export const GitStatusIndicator: React.FC<GitStatusIndicatorProps> = ({
118121
onTooltipMouseEnter={handleTooltipMouseEnter}
119122
onTooltipMouseLeave={handleTooltipMouseLeave}
120123
onContainerRef={handleContainerRef}
124+
isWorking={isWorking}
121125
/>
122126
);
123127
};

src/browser/components/GitStatusIndicatorView.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export interface GitStatusIndicatorViewProps {
3535
onTooltipMouseEnter: () => void;
3636
onTooltipMouseLeave: () => void;
3737
onContainerRef: (el: HTMLSpanElement | null) => void;
38+
/** When true, shows blue pulsing styling to indicate agent is working */
39+
isWorking?: boolean;
3840
}
3941

4042
/**
@@ -57,6 +59,7 @@ export const GitStatusIndicatorView: React.FC<GitStatusIndicatorViewProps> = ({
5759
onTooltipMouseEnter,
5860
onTooltipMouseLeave,
5961
onContainerRef,
62+
isWorking = false,
6063
}) => {
6164
// Handle null gitStatus (loading state)
6265
if (!gitStatus) {
@@ -202,13 +205,20 @@ export const GitStatusIndicatorView: React.FC<GitStatusIndicatorViewProps> = ({
202205
</div>
203206
);
204207

208+
// Dynamic color based on working state
209+
const statusColor = isWorking ? "text-blue-400 animate-pulse" : "text-muted";
210+
const dirtyColor = isWorking ? "text-blue-400" : "text-git-dirty";
211+
205212
return (
206213
<>
207214
<span
208215
ref={onContainerRef}
209216
onMouseEnter={onMouseEnter}
210217
onMouseLeave={onMouseLeave}
211-
className="text-accent relative mr-1.5 flex items-center gap-1 font-mono text-[11px]"
218+
className={cn(
219+
"relative mr-1.5 flex items-center gap-1 font-mono text-[11px] transition-colors",
220+
statusColor
221+
)}
212222
>
213223
{gitStatus.ahead > 0 && (
214224
<span className="flex items-center font-normal">{gitStatus.ahead}</span>
@@ -217,7 +227,7 @@ export const GitStatusIndicatorView: React.FC<GitStatusIndicatorViewProps> = ({
217227
<span className="flex items-center font-normal">{gitStatus.behind}</span>
218228
)}
219229
{gitStatus.dirty && (
220-
<span className="text-git-dirty flex items-center leading-none font-normal">*</span>
230+
<span className={cn("flex items-center leading-none font-normal", dirtyColor)}>*</span>
221231
)}
222232
</span>
223233

src/browser/components/RuntimeBadge.tsx

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { TooltipWrapper, Tooltip } from "./Tooltip";
88
interface RuntimeBadgeProps {
99
runtimeConfig?: RuntimeConfig;
1010
className?: string;
11+
/** When true, shows blue pulsing styling to indicate agent is working */
12+
isWorking?: boolean;
1113
}
1214

1315
/** Server rack icon for SSH runtime */
@@ -56,8 +58,8 @@ function WorktreeIcon() {
5658
);
5759
}
5860

59-
/** Folder icon for local project-dir runtime (reserved for future use) */
60-
function _LocalIcon() {
61+
/** Folder icon for local project-dir runtime */
62+
function LocalIcon() {
6163
return (
6264
<svg
6365
width="10"
@@ -81,18 +83,26 @@ function _LocalIcon() {
8183
* Shows icon-only badge with tooltip describing the runtime type.
8284
* - SSH: server icon with hostname
8385
* - Worktree: git branch icon (isolated worktree)
84-
* - Local: folder icon (project directory, no badge shown by default)
86+
* - Local: folder icon (project directory)
87+
*
88+
* When isWorking=true, badges show blue color with pulse animation.
89+
* When idle, badges show gray styling.
8590
*/
86-
export function RuntimeBadge({ runtimeConfig, className }: RuntimeBadgeProps) {
91+
export function RuntimeBadge({ runtimeConfig, className, isWorking = false }: RuntimeBadgeProps) {
92+
// Dynamic styling based on working state
93+
const workingStyles = isWorking
94+
? "bg-blue-500/20 text-blue-400 border-blue-500/40 animate-pulse"
95+
: "bg-muted/30 text-muted border-muted/50";
96+
8797
// SSH runtime: show server icon with hostname
8898
if (isSSHRuntime(runtimeConfig)) {
8999
const hostname = extractSshHostname(runtimeConfig);
90100
return (
91101
<TooltipWrapper inline>
92102
<span
93103
className={cn(
94-
"inline-flex items-center rounded px-1 py-0.5",
95-
"bg-accent/10 text-accent border border-accent/30",
104+
"inline-flex items-center rounded px-1 py-0.5 border transition-colors",
105+
workingStyles,
96106
className
97107
)}
98108
>
@@ -109,8 +119,8 @@ export function RuntimeBadge({ runtimeConfig, className }: RuntimeBadgeProps) {
109119
<TooltipWrapper inline>
110120
<span
111121
className={cn(
112-
"inline-flex items-center rounded px-1 py-0.5",
113-
"bg-muted/50 text-muted-foreground border border-muted",
122+
"inline-flex items-center rounded px-1 py-0.5 border transition-colors",
123+
workingStyles,
114124
className
115125
)}
116126
>
@@ -121,10 +131,22 @@ export function RuntimeBadge({ runtimeConfig, className }: RuntimeBadgeProps) {
121131
);
122132
}
123133

124-
// Local project-dir runtime: don't show badge (it's the simplest/default)
125-
// Could optionally show LocalIcon if we want visibility
134+
// Local project-dir runtime: show folder icon
126135
if (isLocalProjectRuntime(runtimeConfig)) {
127-
return null; // No badge for simple local runtimes
136+
return (
137+
<TooltipWrapper inline>
138+
<span
139+
className={cn(
140+
"inline-flex items-center rounded px-1 py-0.5 border transition-colors",
141+
workingStyles,
142+
className
143+
)}
144+
>
145+
<LocalIcon />
146+
</span>
147+
<Tooltip align="right">Local: project directory</Tooltip>
148+
</TooltipWrapper>
149+
);
128150
}
129151

130152
return null;

src/browser/components/WorkspaceHeader.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { RuntimeBadge } from "./RuntimeBadge";
44
import { TooltipWrapper, Tooltip } from "./Tooltip";
55
import { formatKeybind, KEYBINDS } from "@/browser/utils/ui/keybinds";
66
import { useGitStatus } from "@/browser/stores/GitStatusStore";
7+
import { useWorkspaceSidebarState } from "@/browser/stores/WorkspaceStore";
78
import type { RuntimeConfig } from "@/common/types/runtime";
8-
import { WorkspaceStatusDot } from "./WorkspaceStatusDot";
99
import { useTutorial } from "@/browser/contexts/TutorialContext";
1010

1111
interface WorkspaceHeaderProps {
@@ -24,6 +24,7 @@ export const WorkspaceHeader: React.FC<WorkspaceHeaderProps> = ({
2424
runtimeConfig,
2525
}) => {
2626
const gitStatus = useGitStatus(workspaceId);
27+
const { canInterrupt } = useWorkspaceSidebarState(workspaceId);
2728
const handleOpenTerminal = useCallback(() => {
2829
void window.api.terminal.openWindow(workspaceId);
2930
}, [workspaceId]);
@@ -46,13 +47,13 @@ export const WorkspaceHeader: React.FC<WorkspaceHeaderProps> = ({
4647
return (
4748
<div className="bg-separator border-border-light flex h-8 items-center justify-between border-b px-[15px] [@media(max-width:768px)]:h-auto [@media(max-width:768px)]:flex-wrap [@media(max-width:768px)]:gap-2 [@media(max-width:768px)]:py-2 [@media(max-width:768px)]:pl-[60px]">
4849
<div className="text-foreground flex min-w-0 items-center gap-2 overflow-hidden font-semibold">
49-
<WorkspaceStatusDot workspaceId={workspaceId} />
50+
<RuntimeBadge runtimeConfig={runtimeConfig} isWorking={canInterrupt} />
5051
<GitStatusIndicator
5152
gitStatus={gitStatus}
5253
workspaceId={workspaceId}
5354
tooltipPosition="bottom"
55+
isWorking={canInterrupt}
5456
/>
55-
<RuntimeBadge runtimeConfig={runtimeConfig} />
5657
<span className="min-w-0 truncate font-mono text-xs">
5758
{projectName} / {branch}
5859
</span>

src/browser/components/WorkspaceListItem.tsx

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import { cn } from "@/common/lib/utils";
33
import { useGitStatus } from "@/browser/stores/GitStatusStore";
44
import { useWorkspaceSidebarState } from "@/browser/stores/WorkspaceStore";
55
import type { FrontendWorkspaceMetadata } from "@/common/types/workspace";
6-
import React, { useCallback, useState } from "react";
6+
import React, { useState } from "react";
77
import { GitStatusIndicator } from "./GitStatusIndicator";
88
import { RuntimeBadge } from "./RuntimeBadge";
99
import { Tooltip, TooltipWrapper } from "./Tooltip";
10-
import { WorkspaceStatusDot } from "./WorkspaceStatusDot";
1110
import { WorkspaceStatusIndicator } from "./WorkspaceStatusIndicator";
1211
import { Shimmer } from "./ai-elements/shimmer";
1312

@@ -24,11 +23,13 @@ export interface WorkspaceListItemProps {
2423
projectName: string;
2524
isSelected: boolean;
2625
isDeleting?: boolean;
27-
lastReadTimestamp: number;
26+
/** @deprecated No longer used since status dot was removed, kept for API compatibility */
27+
lastReadTimestamp?: number;
2828
// Event handlers
2929
onSelectWorkspace: (selection: WorkspaceSelection) => void;
3030
onRemoveWorkspace: (workspaceId: string, button: HTMLElement) => Promise<void>;
31-
onToggleUnread: (workspaceId: string) => void;
31+
/** @deprecated No longer used since status dot was removed, kept for API compatibility */
32+
onToggleUnread?: (workspaceId: string) => void;
3233
}
3334

3435
const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
@@ -37,10 +38,10 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
3738
projectName,
3839
isSelected,
3940
isDeleting,
40-
lastReadTimestamp,
41+
lastReadTimestamp: _lastReadTimestamp,
4142
onSelectWorkspace,
4243
onRemoveWorkspace,
43-
onToggleUnread,
44+
onToggleUnread: _onToggleUnread,
4445
}) => {
4546
// Destructure metadata for convenience
4647
const { id: workspaceId, name: workspaceName, namedWorkspacePath } = metadata;
@@ -93,12 +94,6 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
9394
}
9495
};
9596

96-
// Memoize toggle unread handler to prevent AgentStatusIndicator re-renders
97-
const handleToggleUnread = useCallback(
98-
() => onToggleUnread(workspaceId),
99-
[onToggleUnread, workspaceId]
100-
);
101-
10297
const { canInterrupt } = useWorkspaceSidebarState(workspaceId);
10398

10499
return (
@@ -135,16 +130,9 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
135130
data-workspace-path={namedWorkspacePath}
136131
data-workspace-id={workspaceId}
137132
>
138-
<div>
139-
<WorkspaceStatusDot
140-
workspaceId={workspaceId}
141-
lastReadTimestamp={lastReadTimestamp}
142-
onClick={handleToggleUnread}
143-
/>
144-
</div>
145133
<div className="flex min-w-0 flex-1 flex-col gap-1">
146134
<div className="flex min-w-0 items-center gap-1.5">
147-
<RuntimeBadge runtimeConfig={metadata.runtimeConfig} />
135+
<RuntimeBadge runtimeConfig={metadata.runtimeConfig} isWorking={canInterrupt} />
148136
{isEditing ? (
149137
<input
150138
className="bg-input-bg text-input-text border-input-border font-inherit focus:border-input-border-focus -mx-1 min-w-0 flex-1 rounded-sm border px-1 text-left text-[13px] outline-none"
@@ -181,6 +169,7 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
181169
gitStatus={gitStatus}
182170
workspaceId={workspaceId}
183171
tooltipPosition="right"
172+
isWorking={canInterrupt}
184173
/>
185174

186175
<TooltipWrapper inline>

0 commit comments

Comments
 (0)