feat: rename sidebar-agent-list to sidebar-workspace-list and emit workspace updates on agent removal
This commit is contained in:
@@ -13,7 +13,7 @@ import { StyleSheet, UnistylesRuntime, useUnistyles } from 'react-native-unistyl
|
|||||||
import { Plus, Settings, Users } from 'lucide-react-native'
|
import { Plus, Settings, Users } from 'lucide-react-native'
|
||||||
import { router, usePathname } from 'expo-router'
|
import { router, usePathname } from 'expo-router'
|
||||||
import { usePanelStore } from '@/stores/panel-store'
|
import { usePanelStore } from '@/stores/panel-store'
|
||||||
import { SidebarAgentList } from './sidebar-agent-list'
|
import { SidebarWorkspaceList } from './sidebar-workspace-list'
|
||||||
import { SidebarAgentListSkeleton } from './sidebar-agent-list-skeleton'
|
import { SidebarAgentListSkeleton } from './sidebar-agent-list-skeleton'
|
||||||
import { useSidebarWorkspacesList } from '@/hooks/use-sidebar-workspaces-list'
|
import { useSidebarWorkspacesList } from '@/hooks/use-sidebar-workspaces-list'
|
||||||
import { useSidebarAnimation } from '@/contexts/sidebar-animation-context'
|
import { useSidebarAnimation } from '@/contexts/sidebar-animation-context'
|
||||||
@@ -365,7 +365,7 @@ export function LeftSidebar({ selectedAgentId }: LeftSidebarProps) {
|
|||||||
{isInitialLoad ? (
|
{isInitialLoad ? (
|
||||||
<SidebarAgentListSkeleton />
|
<SidebarAgentListSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<SidebarAgentList
|
<SidebarWorkspaceList
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
serverId={activeServerId}
|
serverId={activeServerId}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
@@ -489,7 +489,7 @@ export function LeftSidebar({ selectedAgentId }: LeftSidebarProps) {
|
|||||||
{isInitialLoad ? (
|
{isInitialLoad ? (
|
||||||
<SidebarAgentListSkeleton />
|
<SidebarAgentListSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<SidebarAgentList
|
<SidebarWorkspaceList
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
serverId={activeServerId}
|
serverId={activeServerId}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
|
|||||||
+180
-22
@@ -1,4 +1,4 @@
|
|||||||
import { View, Text, Pressable, Image, Platform } from 'react-native'
|
import { View, Text, Pressable, Image, Platform, ActivityIndicator, Alert } from 'react-native'
|
||||||
import { useQueries } from '@tanstack/react-query'
|
import { useQueries } from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -31,12 +31,18 @@ import { useKeyboardShortcutsStore } from '@/stores/keyboard-shortcuts-store'
|
|||||||
import { formatTimeAgo } from '@/utils/time'
|
import { formatTimeAgo } from '@/utils/time'
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
|
ContextMenuContent,
|
||||||
|
ContextMenuItem,
|
||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
} from '@/components/ui/context-menu'
|
} from '@/components/ui/context-menu'
|
||||||
|
import { useToast } from '@/contexts/toast-context'
|
||||||
import {
|
import {
|
||||||
buildSidebarWorkspaceViewModel,
|
buildSidebarWorkspaceViewModel,
|
||||||
type SidebarWorkspaceTreeRow,
|
type SidebarWorkspaceTreeRow,
|
||||||
} from '@/utils/sidebar-shortcuts'
|
} from '@/utils/sidebar-shortcuts'
|
||||||
|
import { useCheckoutGitActionsStore } from '@/stores/checkout-git-actions-store'
|
||||||
|
|
||||||
|
const PASEO_WORKTREE_PATH_MARKER = '/.paseo/worktrees'
|
||||||
|
|
||||||
function toProjectIconDataUri(icon: { mimeType: string; data: string } | null): string | null {
|
function toProjectIconDataUri(icon: { mimeType: string; data: string } | null): string | null {
|
||||||
if (!icon) {
|
if (!icon) {
|
||||||
@@ -45,7 +51,7 @@ function toProjectIconDataUri(icon: { mimeType: string; data: string } | null):
|
|||||||
return `data:${icon.mimeType};base64,${icon.data}`
|
return `data:${icon.mimeType};base64,${icon.data}`
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SidebarAgentListProps {
|
interface SidebarWorkspaceListProps {
|
||||||
isOpen?: boolean
|
isOpen?: boolean
|
||||||
projects: SidebarProjectEntry[]
|
projects: SidebarProjectEntry[]
|
||||||
serverId: string | null
|
serverId: string | null
|
||||||
@@ -73,6 +79,8 @@ interface WorkspaceRowProps {
|
|||||||
showShortcutBadge: boolean
|
showShortcutBadge: boolean
|
||||||
onPress: () => void
|
onPress: () => void
|
||||||
drag: () => void
|
drag: () => void
|
||||||
|
isArchiving: boolean
|
||||||
|
useContextMenuTrigger?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveWorkspaceCreatedAtLabel(workspace: SidebarWorkspaceEntry): string | null {
|
function resolveWorkspaceCreatedAtLabel(workspace: SidebarWorkspaceEntry): string | null {
|
||||||
@@ -95,17 +103,33 @@ function resolveStatusDotColor(input: { theme: ReturnType<typeof useUnistyles>['
|
|||||||
: theme.colors.border
|
: theme.colors.border
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPaseoOwnedWorktreePath(path: string): boolean {
|
||||||
|
const normalizedPath = path.replace(/\\/g, '/')
|
||||||
|
const markerIndex = normalizedPath.indexOf(PASEO_WORKTREE_PATH_MARKER)
|
||||||
|
if (markerIndex <= 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const nextChar = normalizedPath[markerIndex + PASEO_WORKTREE_PATH_MARKER.length]
|
||||||
|
return !nextChar || nextChar === '/'
|
||||||
|
}
|
||||||
|
|
||||||
function WorkspaceStatusIndicator({
|
function WorkspaceStatusIndicator({
|
||||||
bucket,
|
bucket,
|
||||||
|
loading = false,
|
||||||
}: {
|
}: {
|
||||||
bucket: SidebarWorkspaceEntry['statusBucket']
|
bucket: SidebarWorkspaceEntry['statusBucket']
|
||||||
|
loading?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useUnistyles()
|
const { theme } = useUnistyles()
|
||||||
const color = resolveStatusDotColor({ theme, bucket })
|
const color = resolveStatusDotColor({ theme, bucket })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.workspaceStatusDot}>
|
<View style={styles.workspaceStatusDot}>
|
||||||
<View style={[styles.workspaceStatusDotFill, { backgroundColor: color }]} />
|
{loading ? (
|
||||||
|
<ActivityIndicator size={8} color={theme.colors.foregroundMuted} />
|
||||||
|
) : (
|
||||||
|
<View style={[styles.workspaceStatusDotFill, { backgroundColor: color }]} />
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -177,7 +201,10 @@ function WorkspaceRow({
|
|||||||
showShortcutBadge,
|
showShortcutBadge,
|
||||||
onPress,
|
onPress,
|
||||||
drag,
|
drag,
|
||||||
|
isArchiving,
|
||||||
|
useContextMenuTrigger = true,
|
||||||
}: WorkspaceRowProps) {
|
}: WorkspaceRowProps) {
|
||||||
|
const { theme } = useUnistyles()
|
||||||
const createdAtLabel = resolveWorkspaceCreatedAtLabel(workspace)
|
const createdAtLabel = resolveWorkspaceCreatedAtLabel(workspace)
|
||||||
const didLongPressRef = useRef(false)
|
const didLongPressRef = useRef(false)
|
||||||
|
|
||||||
@@ -197,22 +224,10 @@ function WorkspaceRow({
|
|||||||
return Platform.OS === 'web' ? null : Gesture.Pan().manualActivation(true).runOnJS(true)
|
return Platform.OS === 'web' ? null : Gesture.Pan().manualActivation(true).runOnJS(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const trigger = (
|
const rowChildren = (
|
||||||
<ContextMenuTrigger
|
<>
|
||||||
enabledOnMobile={false}
|
|
||||||
style={({ pressed, hovered = false }) => [
|
|
||||||
styles.workspaceRow,
|
|
||||||
selected && styles.workspaceRowSelected,
|
|
||||||
hovered && styles.workspaceRowHovered,
|
|
||||||
pressed && styles.workspaceRowPressed,
|
|
||||||
]}
|
|
||||||
onPress={handlePress}
|
|
||||||
onLongPress={handleLongPress}
|
|
||||||
delayLongPress={200}
|
|
||||||
testID={`sidebar-workspace-row-${workspace.workspaceKey}`}
|
|
||||||
>
|
|
||||||
<View style={styles.workspaceRowLeft}>
|
<View style={styles.workspaceRowLeft}>
|
||||||
<WorkspaceStatusIndicator bucket={workspace.statusBucket} />
|
<WorkspaceStatusIndicator bucket={workspace.statusBucket} loading={isArchiving} />
|
||||||
<Text style={styles.workspaceBranchText} numberOfLines={1}>
|
<Text style={styles.workspaceBranchText} numberOfLines={1}>
|
||||||
{workspace.name}
|
{workspace.name}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -229,14 +244,61 @@ function WorkspaceRow({
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</ContextMenuTrigger>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
return moveMonitorGesture ? (
|
const trigger = useContextMenuTrigger ? (
|
||||||
|
<ContextMenuTrigger
|
||||||
|
enabledOnMobile={false}
|
||||||
|
disabled={isArchiving}
|
||||||
|
style={({ pressed, hovered = false }) => [
|
||||||
|
styles.workspaceRow,
|
||||||
|
selected && styles.workspaceRowSelected,
|
||||||
|
hovered && styles.workspaceRowHovered,
|
||||||
|
pressed && styles.workspaceRowPressed,
|
||||||
|
]}
|
||||||
|
onPress={handlePress}
|
||||||
|
onLongPress={handleLongPress}
|
||||||
|
delayLongPress={200}
|
||||||
|
testID={`sidebar-workspace-row-${workspace.workspaceKey}`}
|
||||||
|
>
|
||||||
|
{rowChildren}
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
) : (
|
||||||
|
<Pressable
|
||||||
|
disabled={isArchiving}
|
||||||
|
style={({ pressed, hovered = false }) => [
|
||||||
|
styles.workspaceRow,
|
||||||
|
selected && styles.workspaceRowSelected,
|
||||||
|
hovered && styles.workspaceRowHovered,
|
||||||
|
pressed && styles.workspaceRowPressed,
|
||||||
|
]}
|
||||||
|
onPress={handlePress}
|
||||||
|
onLongPress={handleLongPress}
|
||||||
|
delayLongPress={200}
|
||||||
|
testID={`sidebar-workspace-row-${workspace.workspaceKey}`}
|
||||||
|
>
|
||||||
|
{rowChildren}
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
|
||||||
|
const content = moveMonitorGesture ? (
|
||||||
<GestureDetector gesture={moveMonitorGesture}>{trigger}</GestureDetector>
|
<GestureDetector gesture={moveMonitorGesture}>{trigger}</GestureDetector>
|
||||||
) : (
|
) : (
|
||||||
trigger
|
trigger
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.workspaceRowContainer}>
|
||||||
|
{content}
|
||||||
|
{isArchiving ? (
|
||||||
|
<View style={styles.workspaceArchivingOverlay} testID={`sidebar-workspace-archiving-${workspace.workspaceKey}`}>
|
||||||
|
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
|
||||||
|
<Text style={styles.workspaceArchivingText}>Archiving</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function WorkspaceRowWithMenu({
|
function WorkspaceRowWithMenu({
|
||||||
@@ -254,6 +316,72 @@ function WorkspaceRowWithMenu({
|
|||||||
onPress: () => void
|
onPress: () => void
|
||||||
drag: () => void
|
drag: () => void
|
||||||
}) {
|
}) {
|
||||||
|
const toast = useToast()
|
||||||
|
const archiveWorktree = useCheckoutGitActionsStore((state) => state.archiveWorktree)
|
||||||
|
const archiveStatus = useCheckoutGitActionsStore((state) =>
|
||||||
|
state.getStatus({
|
||||||
|
serverId: workspace.serverId,
|
||||||
|
cwd: workspace.workspaceId,
|
||||||
|
actionId: 'archive-worktree',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const isArchiving = archiveStatus === 'pending'
|
||||||
|
const isPaseoOwnedWorktree = isPaseoOwnedWorktreePath(workspace.workspaceId)
|
||||||
|
|
||||||
|
const handleArchiveWorktree = useCallback(() => {
|
||||||
|
if (isArchiving) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Alert.alert(
|
||||||
|
'Archive worktree?',
|
||||||
|
`Archive "${workspace.name}"?\n\nThis removes the worktree from the sidebar.`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'Cancel',
|
||||||
|
style: 'cancel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Archive',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: () => {
|
||||||
|
void archiveWorktree({
|
||||||
|
serverId: workspace.serverId,
|
||||||
|
cwd: workspace.workspaceId,
|
||||||
|
worktreePath: workspace.workspaceId,
|
||||||
|
}).catch((error) => {
|
||||||
|
const message = error instanceof Error ? error.message : 'Failed to archive worktree'
|
||||||
|
toast.error(message)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ cancelable: true }
|
||||||
|
)
|
||||||
|
}, [
|
||||||
|
archiveWorktree,
|
||||||
|
isArchiving,
|
||||||
|
toast,
|
||||||
|
workspace.name,
|
||||||
|
workspace.serverId,
|
||||||
|
workspace.workspaceId,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!isPaseoOwnedWorktree) {
|
||||||
|
return (
|
||||||
|
<WorkspaceRow
|
||||||
|
workspace={workspace}
|
||||||
|
selected={selected}
|
||||||
|
shortcutNumber={shortcutNumber}
|
||||||
|
showShortcutBadge={showShortcutBadge}
|
||||||
|
onPress={onPress}
|
||||||
|
drag={drag}
|
||||||
|
isArchiving={false}
|
||||||
|
useContextMenuTrigger={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<WorkspaceRow
|
<WorkspaceRow
|
||||||
@@ -263,7 +391,19 @@ function WorkspaceRowWithMenu({
|
|||||||
showShortcutBadge={showShortcutBadge}
|
showShortcutBadge={showShortcutBadge}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
drag={drag}
|
drag={drag}
|
||||||
|
isArchiving={isArchiving}
|
||||||
/>
|
/>
|
||||||
|
<ContextMenuContent align="start" width={220} testID={`sidebar-workspace-context-${workspace.workspaceKey}`}>
|
||||||
|
<ContextMenuItem
|
||||||
|
testID={`sidebar-workspace-context-${workspace.workspaceKey}-archive`}
|
||||||
|
status={archiveStatus}
|
||||||
|
pendingLabel="Archiving..."
|
||||||
|
destructive
|
||||||
|
onSelect={handleArchiveWorktree}
|
||||||
|
>
|
||||||
|
Archive worktree
|
||||||
|
</ContextMenuItem>
|
||||||
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -290,7 +430,7 @@ function hasVisibleOrderChanged(input: {
|
|||||||
return input.reorderedVisibleKeys.some((key, index) => currentVisible[index] !== key)
|
return input.reorderedVisibleKeys.some((key, index) => currentVisible[index] !== key)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SidebarAgentList({
|
export function SidebarWorkspaceList({
|
||||||
isOpen = true,
|
isOpen = true,
|
||||||
projects,
|
projects,
|
||||||
serverId,
|
serverId,
|
||||||
@@ -299,7 +439,7 @@ export function SidebarAgentList({
|
|||||||
onWorkspacePress,
|
onWorkspacePress,
|
||||||
listFooterComponent,
|
listFooterComponent,
|
||||||
parentGestureRef,
|
parentGestureRef,
|
||||||
}: SidebarAgentListProps) {
|
}: SidebarWorkspaceListProps) {
|
||||||
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
|
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
|
||||||
const showDesktopWebScrollbar = Platform.OS === 'web' && !isMobile
|
const showDesktopWebScrollbar = Platform.OS === 'web' && !isMobile
|
||||||
const segments = useSegments()
|
const segments = useSegments()
|
||||||
@@ -692,6 +832,9 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
workspaceRowSelected: {
|
workspaceRowSelected: {
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface2,
|
||||||
},
|
},
|
||||||
|
workspaceRowContainer: {
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
workspaceStatusDot: {
|
workspaceStatusDot: {
|
||||||
width: 8,
|
width: 8,
|
||||||
height: 8,
|
height: 8,
|
||||||
@@ -706,6 +849,21 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
height: 8,
|
height: 8,
|
||||||
borderRadius: theme.borderRadius.full,
|
borderRadius: theme.borderRadius.full,
|
||||||
},
|
},
|
||||||
|
workspaceArchivingOverlay: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
borderRadius: theme.borderRadius.lg,
|
||||||
|
backgroundColor: `${theme.colors.surface0}cc`,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
workspaceArchivingText: {
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
workspaceBranchText: {
|
workspaceBranchText: {
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
@@ -4544,10 +4544,12 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const removedAgents = new Set<string>()
|
const removedAgents = new Set<string>()
|
||||||
|
const affectedWorkspaceCwds = new Set<string>([targetPath])
|
||||||
const agents = this.agentManager.listAgents()
|
const agents = this.agentManager.listAgents()
|
||||||
for (const agent of agents) {
|
for (const agent of agents) {
|
||||||
if (this.isPathWithinRoot(targetPath, agent.cwd)) {
|
if (this.isPathWithinRoot(targetPath, agent.cwd)) {
|
||||||
removedAgents.add(agent.id)
|
removedAgents.add(agent.id)
|
||||||
|
affectedWorkspaceCwds.add(agent.cwd)
|
||||||
try {
|
try {
|
||||||
await this.agentManager.closeAgent(agent.id)
|
await this.agentManager.closeAgent(agent.id)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -4565,6 +4567,7 @@ export class Session {
|
|||||||
for (const record of registryRecords) {
|
for (const record of registryRecords) {
|
||||||
if (this.isPathWithinRoot(targetPath, record.cwd)) {
|
if (this.isPathWithinRoot(targetPath, record.cwd)) {
|
||||||
removedAgents.add(record.id)
|
removedAgents.add(record.id)
|
||||||
|
affectedWorkspaceCwds.add(record.cwd)
|
||||||
try {
|
try {
|
||||||
await this.agentStorage.remove(record.id)
|
await this.agentStorage.remove(record.id)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -4591,6 +4594,8 @@ export class Session {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.emitWorkspaceUpdatesForCwds(affectedWorkspaceCwds)
|
||||||
|
|
||||||
return Array.from(removedAgents)
|
return Array.from(removedAgents)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5671,6 +5676,25 @@ export class Session {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async emitWorkspaceUpdatesForCwds(cwds: Iterable<string>): Promise<void> {
|
||||||
|
if (!this.workspaceUpdatesSubscription) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueWorkspaceCwds = new Set<string>()
|
||||||
|
for (const cwd of cwds) {
|
||||||
|
const normalized = this.normalizeWorkspaceId(cwd)
|
||||||
|
if (!normalized) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
uniqueWorkspaceCwds.add(normalized)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const workspaceCwd of uniqueWorkspaceCwds) {
|
||||||
|
await this.emitWorkspaceUpdateForCwd(workspaceCwd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async handleFetchAgents(
|
private async handleFetchAgents(
|
||||||
request: Extract<SessionInboundMessage, { type: 'fetch_agents_request' }>
|
request: Extract<SessionInboundMessage, { type: 'fetch_agents_request' }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|||||||
@@ -264,4 +264,29 @@ describe('workspace aggregation', () => {
|
|||||||
id: '/tmp/repo',
|
id: '/tmp/repo',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('workspace update fanout for multiple cwd values is deduplicated', async () => {
|
||||||
|
const session = createSessionForWorkspaceTests() as any
|
||||||
|
session.workspaceUpdatesSubscription = {
|
||||||
|
subscriptionId: 'sub-dedupe',
|
||||||
|
filter: undefined,
|
||||||
|
isBootstrapping: false,
|
||||||
|
pendingUpdatesByWorkspaceId: new Map(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const emitWorkspaceUpdateForCwd = vi.fn(async () => {})
|
||||||
|
session.emitWorkspaceUpdateForCwd = emitWorkspaceUpdateForCwd
|
||||||
|
|
||||||
|
await session.emitWorkspaceUpdatesForCwds([
|
||||||
|
'/tmp/repo',
|
||||||
|
'/tmp/repo/',
|
||||||
|
' /tmp/repo ',
|
||||||
|
'/tmp/repo/sub',
|
||||||
|
'/tmp/repo/sub/',
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(emitWorkspaceUpdateForCwd).toHaveBeenCalledTimes(2)
|
||||||
|
expect(emitWorkspaceUpdateForCwd).toHaveBeenNthCalledWith(1, '/tmp/repo')
|
||||||
|
expect(emitWorkspaceUpdateForCwd).toHaveBeenNthCalledWith(2, '/tmp/repo/sub')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user