Increase workspace status emphasis and use amber alert for needs input

This commit is contained in:
Mohamed Boudra
2026-04-02 22:53:52 +07:00
parent a854096c35
commit 994ee488b9
3 changed files with 75 additions and 10 deletions
@@ -27,6 +27,7 @@ import { type GestureType } from "react-native-gesture-handler";
import * as Clipboard from "expo-clipboard"; import * as Clipboard from "expo-clipboard";
import { import {
Archive, Archive,
CircleAlert,
ChevronDown, ChevronDown,
ChevronRight, ChevronRight,
Copy, Copy,
@@ -73,7 +74,7 @@ import { decideLongPressMove } from "@/utils/sidebar-gesture-arbitration";
import { confirmDialog } from "@/utils/confirm-dialog"; import { confirmDialog } from "@/utils/confirm-dialog";
import { projectIconPlaceholderLabelFromDisplayName } from "@/utils/project-display-name"; import { projectIconPlaceholderLabelFromDisplayName } from "@/utils/project-display-name";
import { shouldRenderSyncedStatusLoader } from "@/utils/status-loader"; import { shouldRenderSyncedStatusLoader } from "@/utils/status-loader";
import { getStatusDotColor } from "@/utils/status-dot-color"; import { getStatusDotColor, isEmphasizedStatusDotBucket } from "@/utils/status-dot-color";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Shortcut } from "@/components/ui/shortcut"; import { Shortcut } from "@/components/ui/shortcut";
@@ -100,6 +101,10 @@ const workspaceKeyExtractor = (workspace: SidebarWorkspaceEntry) => workspace.wo
const projectKeyExtractor = (project: SidebarProjectEntry) => project.projectKey; const projectKeyExtractor = (project: SidebarProjectEntry) => project.projectKey;
const EMPTY_WORKSPACES = new Map(); const EMPTY_WORKSPACES = new Map();
const WORKSPACE_STATUS_DOT_WIDTH = 14; const WORKSPACE_STATUS_DOT_WIDTH = 14;
const DEFAULT_STATUS_DOT_SIZE = 7;
const EMPHASIZED_STATUS_DOT_SIZE = 9;
const DEFAULT_STATUS_DOT_OFFSET = 0;
const EMPHASIZED_STATUS_DOT_OFFSET = -1;
const GITHUB_PR_STATE_LABELS: Record<PrHint["state"], string> = { const GITHUB_PR_STATE_LABELS: Record<PrHint["state"], string> = {
open: "Open", open: "Open",
merged: "Merged", merged: "Merged",
@@ -238,6 +243,14 @@ function WorkspaceStatusIndicator({
); );
} }
if (bucket === "needs_input") {
return (
<View style={styles.workspaceStatusDot}>
<CircleAlert size={14} color={theme.colors.palette.amber[500]} />
</View>
);
}
const KindIcon = const KindIcon =
workspaceKind === "local_checkout" workspaceKind === "local_checkout"
? Monitor ? Monitor
@@ -247,6 +260,13 @@ function WorkspaceStatusIndicator({
if (!KindIcon) return null; if (!KindIcon) return null;
const dotColor = getStatusDotColor({ theme, bucket, showDoneAsInactive: false }); const dotColor = getStatusDotColor({ theme, bucket, showDoneAsInactive: false });
const statusDotSize = isEmphasizedStatusDotBucket(bucket)
? EMPHASIZED_STATUS_DOT_SIZE
: DEFAULT_STATUS_DOT_SIZE;
const statusDotOffset =
statusDotSize === EMPHASIZED_STATUS_DOT_SIZE
? EMPHASIZED_STATUS_DOT_OFFSET
: DEFAULT_STATUS_DOT_OFFSET;
return ( return (
<View style={styles.workspaceStatusDot}> <View style={styles.workspaceStatusDot}>
@@ -258,6 +278,10 @@ function WorkspaceStatusIndicator({
{ {
backgroundColor: dotColor, backgroundColor: dotColor,
borderColor: theme.colors.surface0, borderColor: theme.colors.surface0,
width: statusDotSize,
height: statusDotSize,
right: statusDotOffset,
bottom: statusDotOffset,
}, },
]} ]}
/> />
@@ -327,11 +351,26 @@ function ProjectLeadingVisual({
); );
} }
if (activeWorkspace.statusBucket === "needs_input") {
return (
<View style={styles.projectLeadingVisualSlot}>
<CircleAlert size={14} color={theme.colors.palette.amber[500]} />
</View>
);
}
const dotColor = getStatusDotColor({ const dotColor = getStatusDotColor({
theme, theme,
bucket: activeWorkspace.statusBucket, bucket: activeWorkspace.statusBucket,
showDoneAsInactive: false, showDoneAsInactive: false,
}); });
const statusDotSize = isEmphasizedStatusDotBucket(activeWorkspace.statusBucket)
? EMPHASIZED_STATUS_DOT_SIZE
: DEFAULT_STATUS_DOT_SIZE;
const statusDotOffset =
statusDotSize === EMPHASIZED_STATUS_DOT_SIZE
? EMPHASIZED_STATUS_DOT_OFFSET
: DEFAULT_STATUS_DOT_OFFSET;
return ( return (
<View style={styles.projectLeadingVisualSlot}> <View style={styles.projectLeadingVisualSlot}>
@@ -343,6 +382,10 @@ function ProjectLeadingVisual({
{ {
backgroundColor: dotColor, backgroundColor: dotColor,
borderColor: theme.colors.surface0, borderColor: theme.colors.surface0,
width: statusDotSize,
height: statusDotSize,
right: statusDotOffset,
bottom: statusDotOffset,
}, },
]} ]}
/> />
@@ -2184,10 +2227,10 @@ const styles = StyleSheet.create((theme) => ({
}, },
statusDotOverlay: { statusDotOverlay: {
position: "absolute", position: "absolute",
right: 0, right: DEFAULT_STATUS_DOT_OFFSET,
bottom: 0, bottom: DEFAULT_STATUS_DOT_OFFSET,
width: 7, width: DEFAULT_STATUS_DOT_SIZE,
height: 7, height: DEFAULT_STATUS_DOT_SIZE,
borderRadius: theme.borderRadius.full, borderRadius: theme.borderRadius.full,
borderWidth: 1, borderWidth: 1,
}, },
@@ -8,7 +8,7 @@ import { ensurePanelsRegistered } from "@/panels/register-panels";
import { getPanelRegistration } from "@/panels/panel-registry"; import { getPanelRegistration } from "@/panels/panel-registry";
import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types"; import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types";
import type { SidebarStateBucket } from "@/utils/sidebar-agent-state"; import type { SidebarStateBucket } from "@/utils/sidebar-agent-state";
import { getStatusDotColor } from "@/utils/status-dot-color"; import { getStatusDotColor, isEmphasizedStatusDotBucket } from "@/utils/status-dot-color";
import { shouldRenderSyncedStatusLoader } from "@/utils/status-loader"; import { shouldRenderSyncedStatusLoader } from "@/utils/status-loader";
export interface WorkspaceTabPresentation { export interface WorkspaceTabPresentation {
@@ -21,6 +21,11 @@ export interface WorkspaceTabPresentation {
statusBucket: SidebarStateBucket | null; statusBucket: SidebarStateBucket | null;
} }
const DEFAULT_STATUS_DOT_SIZE = 7;
const EMPHASIZED_STATUS_DOT_SIZE = 9;
const DEFAULT_STATUS_DOT_OFFSET = -2;
const EMPHASIZED_STATUS_DOT_OFFSET = -3;
type WorkspaceTabPresentationResolverProps = { type WorkspaceTabPresentationResolverProps = {
tab: WorkspaceTabDescriptor; tab: WorkspaceTabDescriptor;
serverId: string; serverId: string;
@@ -114,6 +119,13 @@ export function WorkspaceTabIcon({
bucket: presentation.statusBucket, bucket: presentation.statusBucket,
showDoneAsInactive: false, showDoneAsInactive: false,
}); });
const statusDotSize = isEmphasizedStatusDotBucket(presentation.statusBucket)
? EMPHASIZED_STATUS_DOT_SIZE
: DEFAULT_STATUS_DOT_SIZE;
const statusDotOffset =
statusDotSize === EMPHASIZED_STATUS_DOT_SIZE
? EMPHASIZED_STATUS_DOT_OFFSET
: DEFAULT_STATUS_DOT_OFFSET;
const shouldShowLoader = shouldRenderSyncedStatusLoader({ const shouldShowLoader = shouldRenderSyncedStatusLoader({
bucket: presentation.statusBucket, bucket: presentation.statusBucket,
}); });
@@ -137,6 +149,10 @@ export function WorkspaceTabIcon({
{ {
backgroundColor: statusDotColor, backgroundColor: statusDotColor,
borderColor: statusDotBorderColor ?? theme.colors.surface0, borderColor: statusDotBorderColor ?? theme.colors.surface0,
width: statusDotSize,
height: statusDotSize,
right: statusDotOffset,
bottom: statusDotOffset,
}, },
]} ]}
/> />
@@ -199,10 +215,10 @@ const styles = StyleSheet.create((theme) => ({
}, },
statusDot: { statusDot: {
position: "absolute", position: "absolute",
right: -2, right: DEFAULT_STATUS_DOT_OFFSET,
bottom: -2, bottom: DEFAULT_STATUS_DOT_OFFSET,
width: 7, width: DEFAULT_STATUS_DOT_SIZE,
height: 7, height: DEFAULT_STATUS_DOT_SIZE,
borderRadius: theme.borderRadius.full, borderRadius: theme.borderRadius.full,
borderWidth: 1, borderWidth: 1,
}, },
@@ -25,3 +25,9 @@ export function getStatusDotColor(input: {
} }
return null; return null;
} }
export function isEmphasizedStatusDotBucket(
bucket: SidebarStateBucket | null | undefined,
): boolean {
return bucket === "needs_input" || bucket === "attention";
}