Skip to content

Commit d2132e3

Browse files
committed
🤖 feat: add colored borders to runtime badges for better discrimination
- SSH: blue theme (idle and working) - Worktree: purple theme (idle and working) - Local: gray theme (idle and working) Each runtime type maintains consistent coloring, with working state showing brighter colors and pulse animation. Added RuntimeBadgeVariations story showing all 6 states. Added createLocalWorkspace helper to mockFactory. _Generated with `mux`_
1 parent 38d0f2c commit d2132e3

File tree

3 files changed

+155
-13
lines changed

3 files changed

+155
-13
lines changed

src/browser/components/RuntimeBadge.tsx

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,31 +78,42 @@ function LocalIcon() {
7878
);
7979
}
8080

81+
// Runtime-specific color schemes - each type has consistent colors in idle/working states
82+
const RUNTIME_STYLES = {
83+
ssh: {
84+
idle: "bg-blue-500/10 text-blue-400/70 border-blue-500/40",
85+
working: "bg-blue-500/20 text-blue-400 border-blue-500/50 animate-pulse",
86+
},
87+
worktree: {
88+
idle: "bg-purple-500/10 text-purple-400/70 border-purple-500/40",
89+
working: "bg-purple-500/20 text-purple-400 border-purple-500/50 animate-pulse",
90+
},
91+
local: {
92+
idle: "bg-muted/20 text-muted/70 border-muted/40",
93+
working: "bg-muted/30 text-muted border-muted/50 animate-pulse",
94+
},
95+
} as const;
96+
8197
/**
8298
* Badge to display runtime type information.
8399
* Shows icon-only badge with tooltip describing the runtime type.
84-
* - SSH: server icon with hostname
85-
* - Worktree: git branch icon (isolated worktree)
86-
* - Local: folder icon (project directory)
100+
* - SSH: server icon with hostname (blue theme)
101+
* - Worktree: git branch icon (purple theme)
102+
* - Local: folder icon (gray theme)
87103
*
88-
* When isWorking=true, badges show blue color with pulse animation.
89-
* When idle, badges show gray styling.
104+
* When isWorking=true, badges brighten and pulse within their color scheme.
90105
*/
91106
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-
97107
// SSH runtime: show server icon with hostname
98108
if (isSSHRuntime(runtimeConfig)) {
99109
const hostname = extractSshHostname(runtimeConfig);
110+
const styles = isWorking ? RUNTIME_STYLES.ssh.working : RUNTIME_STYLES.ssh.idle;
100111
return (
101112
<TooltipWrapper inline>
102113
<span
103114
className={cn(
104115
"inline-flex items-center rounded px-1 py-0.5 border transition-colors",
105-
workingStyles,
116+
styles,
106117
className
107118
)}
108119
>
@@ -115,12 +126,13 @@ export function RuntimeBadge({ runtimeConfig, className, isWorking = false }: Ru
115126

116127
// Worktree runtime: show git branch icon
117128
if (isWorktreeRuntime(runtimeConfig)) {
129+
const styles = isWorking ? RUNTIME_STYLES.worktree.working : RUNTIME_STYLES.worktree.idle;
118130
return (
119131
<TooltipWrapper inline>
120132
<span
121133
className={cn(
122134
"inline-flex items-center rounded px-1 py-0.5 border transition-colors",
123-
workingStyles,
135+
styles,
124136
className
125137
)}
126138
>
@@ -133,12 +145,13 @@ export function RuntimeBadge({ runtimeConfig, className, isWorking = false }: Ru
133145

134146
// Local project-dir runtime: show folder icon
135147
if (isLocalProjectRuntime(runtimeConfig)) {
148+
const styles = isWorking ? RUNTIME_STYLES.local.working : RUNTIME_STYLES.local.idle;
136149
return (
137150
<TooltipWrapper inline>
138151
<span
139152
className={cn(
140153
"inline-flex items-center rounded px-1 py-0.5 border transition-colors",
141-
workingStyles,
154+
styles,
142155
className
143156
)}
144157
>

src/browser/stories/App.sidebar.stories.tsx

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
import { appMeta, AppWithMocks, type AppStory } from "./meta.js";
66
import {
77
NOW,
8+
STABLE_TIMESTAMP,
89
createWorkspace,
910
createSSHWorkspace,
11+
createLocalWorkspace,
12+
createUserMessage,
13+
createStreamingChatHandler,
1014
groupWorkspacesByProject,
1115
createMockAPI,
1216
installMockAPI,
@@ -175,3 +179,118 @@ export const GitStatusVariations: AppStory = {
175179
/>
176180
),
177181
};
182+
183+
/**
184+
* All runtime badge variations showing different runtime types.
185+
* Each type has distinct colors:
186+
* - SSH: blue theme
187+
* - Worktree: purple theme
188+
* - Local: gray theme
189+
*
190+
* The streaming workspaces show the "working" state with pulse animation.
191+
*/
192+
export const RuntimeBadgeVariations: AppStory = {
193+
render: () => (
194+
<AppWithMocks
195+
setup={() => {
196+
// Idle workspaces (one of each type)
197+
const sshIdle = createSSHWorkspace({
198+
id: "ws-ssh-idle",
199+
name: "ssh-idle",
200+
projectName: "runtime-demo",
201+
host: "dev.example.com",
202+
createdAt: new Date(NOW - 3600000).toISOString(),
203+
});
204+
const worktreeIdle = createWorkspace({
205+
id: "ws-worktree-idle",
206+
name: "worktree-idle",
207+
projectName: "runtime-demo",
208+
createdAt: new Date(NOW - 7200000).toISOString(),
209+
});
210+
const localIdle = createLocalWorkspace({
211+
id: "ws-local-idle",
212+
name: "local-idle",
213+
projectName: "runtime-demo",
214+
createdAt: new Date(NOW - 10800000).toISOString(),
215+
});
216+
217+
// Working workspaces (streaming - shows pulse animation)
218+
const sshWorking = createSSHWorkspace({
219+
id: "ws-ssh-working",
220+
name: "ssh-working",
221+
projectName: "runtime-demo",
222+
host: "prod.example.com",
223+
createdAt: new Date(NOW - 1800000).toISOString(),
224+
});
225+
const worktreeWorking = createWorkspace({
226+
id: "ws-worktree-working",
227+
name: "worktree-working",
228+
projectName: "runtime-demo",
229+
createdAt: new Date(NOW - 900000).toISOString(),
230+
});
231+
const localWorking = createLocalWorkspace({
232+
id: "ws-local-working",
233+
name: "local-working",
234+
projectName: "runtime-demo",
235+
createdAt: new Date(NOW - 300000).toISOString(),
236+
});
237+
238+
const workspaces = [
239+
sshIdle,
240+
worktreeIdle,
241+
localIdle,
242+
sshWorking,
243+
worktreeWorking,
244+
localWorking,
245+
];
246+
247+
// Create streaming handlers for working workspaces
248+
const workingMessage = createUserMessage("msg-1", "Working on task...", {
249+
historySequence: 1,
250+
timestamp: STABLE_TIMESTAMP,
251+
});
252+
253+
const chatHandlers = new Map([
254+
[
255+
"ws-ssh-working",
256+
createStreamingChatHandler({
257+
messages: [workingMessage],
258+
streamingMessageId: "stream-ssh",
259+
model: "claude-sonnet-4-20250514",
260+
historySequence: 2,
261+
streamText: "Processing SSH task...",
262+
}),
263+
],
264+
[
265+
"ws-worktree-working",
266+
createStreamingChatHandler({
267+
messages: [workingMessage],
268+
streamingMessageId: "stream-worktree",
269+
model: "claude-sonnet-4-20250514",
270+
historySequence: 2,
271+
streamText: "Processing worktree task...",
272+
}),
273+
],
274+
[
275+
"ws-local-working",
276+
createStreamingChatHandler({
277+
messages: [workingMessage],
278+
streamingMessageId: "stream-local",
279+
model: "claude-sonnet-4-20250514",
280+
historySequence: 2,
281+
streamText: "Processing local task...",
282+
}),
283+
],
284+
]);
285+
286+
installMockAPI(
287+
createMockAPI({
288+
projects: groupWorkspacesByProject(workspaces),
289+
workspaces,
290+
chatHandlers,
291+
})
292+
);
293+
}}
294+
/>
295+
),
296+
};

src/browser/stories/mockFactory.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ export function createSSHWorkspace(
7777
});
7878
}
7979

80+
/** Create local project-dir workspace (no isolation, uses project path directly) */
81+
export function createLocalWorkspace(
82+
opts: Partial<WorkspaceFixture> & { id: string; name: string; projectName: string }
83+
): FrontendWorkspaceMetadata {
84+
return createWorkspace({
85+
...opts,
86+
runtimeConfig: { type: "local" },
87+
});
88+
}
89+
8090
/** Create workspace with incompatible runtime (for downgrade testing) */
8191
export function createIncompatibleWorkspace(
8292
opts: Partial<WorkspaceFixture> & {

0 commit comments

Comments
 (0)