Skip to content

Commit a2026f5

Browse files
committed
fix(chats): reconcile partial deletes and collapsed selection
1 parent 1821af5 commit a2026f5

7 files changed

Lines changed: 67 additions & 20 deletions

File tree

‎apps/sim/app/o/[organizationId]/components/organization-sidebar/components/chats-section/chats-section.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ export function ChatsSection({
141141
key={chat.id}
142142
chat={chat}
143143
isCurrentRoute={pathname === chat.href}
144+
isSelected={selectedChats.size > 1 && selectedChats.has(chat.id)}
145+
onSelectChat={actions.handleChatClick}
144146
isMenuOpen={menuOpenChatId === chat.id}
145147
isEditing={rename.editingId === chat.id}
146148
editValue={rename.value}

‎apps/sim/app/o/[organizationId]/components/organization-sidebar/hooks/use-organization-chat-actions.ts‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useChatSelection } from '@/app/workspace/[workspaceId]/w/components/sid
88
import { useFlyoutInlineRename } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-flyout-inline-rename'
99
import { useHoverMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-hover-menu'
1010
import {
11+
MothershipChatDeleteError,
1112
useDeleteMothershipChat,
1213
useDeleteMothershipChats,
1314
useMarkMothershipChatRead,
@@ -124,19 +125,27 @@ export function useOrganizationChatActions({
124125

125126
const confirmDelete = useCallback(() => {
126127
if (chatsToDelete.length === 0 || isDeleting) return
128+
const redirectIfDeleted = (ids: string[]) => {
129+
if (
130+
ids.some((id) => window.location.pathname === organizationRoutes(organizationId).chat(id))
131+
) {
132+
router.push(organizationRoutes(organizationId).home)
133+
}
134+
}
127135
const options = {
128136
onSuccess: () => {
129137
setChatIdsToDelete([])
130138
useFolderStore.getState().clearChatSelection()
131-
if (
132-
chatIdsToDelete.some(
133-
(id) => window.location.pathname === organizationRoutes(organizationId).chat(id)
134-
)
135-
) {
136-
router.push(organizationRoutes(organizationId).home)
139+
redirectIfDeleted(chatIdsToDelete)
140+
},
141+
onError: (error: Error) => {
142+
if (error instanceof MothershipChatDeleteError) {
143+
const deletedIds = new Set(error.deletedChatIds)
144+
setChatIdsToDelete((ids) => ids.filter((id) => !deletedIds.has(id)))
145+
redirectIfDeleted(error.deletedChatIds)
137146
}
147+
toast.error(error.message)
138148
},
139-
onError: (error: Error) => toast.error(error.message),
140149
}
141150
if (chatsToDelete.length === 1) {
142151
deleteChat.mutate(chatsToDelete[0].id, options)

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/chat-navigation-link/chat-navigation-link.tsx‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,15 @@ function IntentAwareChatNavigationLink({
137137
}}
138138
onTouchStart={onTouchStart}
139139
onClick={(event) => {
140+
const isSelectionClick =
141+
!event.defaultPrevented && !event.metaKey && !event.ctrlKey && !event.altKey
142+
if (isSelectionClick && event.shiftKey && onSelectChat) {
143+
event.preventDefault()
144+
onSelectChat(chatId, true)
145+
}
140146
onClick?.(event)
141-
if (
142-
!event.defaultPrevented &&
143-
!event.metaKey &&
144-
!event.ctrlKey &&
145-
!event.altKey &&
146-
onSelectChat
147-
) {
148-
if (event.shiftKey) event.preventDefault()
149-
onSelectChat(chatId, event.shiftKey)
147+
if (isSelectionClick && !event.defaultPrevented) {
148+
onSelectChat?.(chatId, false)
150149
}
151150
if (
152151
!event.defaultPrevented &&

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/collapsed-sidebar-menu/collapsed-sidebar-menu.tsx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ type CollapsedSidebarMenuProps = {
156156
interface CollapsedChatFlyoutItemProps {
157157
chat: { id: string; href: string; name: string; isActive?: boolean; isUnread?: boolean }
158158
isCurrentRoute: boolean
159+
isSelected?: boolean
160+
onSelectChat?: (chatId: string, shiftKey: boolean) => void
159161
isMenuOpen?: boolean
160162
isEditing?: boolean
161163
editValue?: string
@@ -284,6 +286,8 @@ export function CollapsedSidebarMenu({
284286
export function CollapsedChatFlyoutItem({
285287
chat,
286288
isCurrentRoute,
289+
isSelected = false,
290+
onSelectChat,
287291
isMenuOpen = false,
288292
isEditing = false,
289293
editValue,
@@ -330,7 +334,7 @@ export function CollapsedChatFlyoutItem({
330334
return (
331335
<DropdownMenuItem
332336
asChild
333-
active={isCurrentRoute || isMenuOpen}
337+
active={isCurrentRoute || isSelected || isMenuOpen}
334338
actionOpen={isMenuOpen}
335339
action={
336340
showActions ? (
@@ -348,6 +352,7 @@ export function CollapsedChatFlyoutItem({
348352
chatId={chat.id}
349353
href={chat.href}
350354
isCurrentRoute={isCurrentRoute}
355+
onSelectChat={onSelectChat}
351356
onContextMenu={
352357
chat.id !== 'new' && onContextMenu ? (e) => onContextMenu(e, chat.id) : undefined
353358
}

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ import { useFolderMap, useFolders } from '@/hooks/queries/folders'
125125
import { type LogFilters, useLogsList } from '@/hooks/queries/logs'
126126
import type { MothershipChatMetadata } from '@/hooks/queries/mothership-chats'
127127
import {
128+
MothershipChatDeleteError,
128129
useDeleteMothershipChat,
129130
useDeleteMothershipChats,
130131
useMarkMothershipChatRead,
@@ -887,7 +888,19 @@ export const Sidebar = memo(function Sidebar({ organizationHref }: SidebarProps)
887888
if (chatIdsToDelete.length === 1) {
888889
deleteChatMutation.mutate(chatIdsToDelete[0], { onSuccess: onDeleteSuccess })
889890
} else {
890-
deleteChatsMutation.mutate(chatIdsToDelete, { onSuccess: onDeleteSuccess })
891+
deleteChatsMutation.mutate(chatIdsToDelete, {
892+
onSuccess: onDeleteSuccess,
893+
onError: (error) => {
894+
if (
895+
error instanceof MothershipChatDeleteError &&
896+
error.deletedChatIds.some(
897+
(id) => window.location.pathname === `/workspace/${workspaceId}/chat/${id}`
898+
)
899+
) {
900+
router.push(`/workspace/${workspaceId}/home`)
901+
}
902+
},
903+
})
891904
}
892905
setIsChatDeleteModalOpen(false)
893906
}
@@ -1445,6 +1458,8 @@ export const Sidebar = memo(function Sidebar({ organizationHref }: SidebarProps)
14451458
key={chat.id}
14461459
chat={chat}
14471460
isCurrentRoute={pathname === chat.href}
1461+
isSelected={hasChatMultiSelection && selectedChats.has(chat.id)}
1462+
onSelectChat={handleChatClick}
14481463
isMenuOpen={menuOpenChatId === chat.id}
14491464
isEditing={chat.id === chatFlyoutRename.editingId}
14501465
editValue={chatFlyoutRename.value}

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/utils.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ export function isSidebarBackgroundClick(event: MouseEvent<HTMLElement>): boolea
1010
return (
1111
event.target instanceof Element &&
1212
event.currentTarget.contains(event.target) &&
13-
!event.target.closest('button, [role="button"], a')
13+
!event.target.closest(
14+
'a, button, input, textarea, select, [role="button"], [contenteditable]:not([contenteditable="false"])'
15+
)
1416
)
1517
}
1618

‎apps/sim/hooks/queries/mothership-chats.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { toError } from '@sim/utils/errors'
12
import { isRecordLike } from '@sim/utils/object'
23
import {
34
keepPreviousData,
@@ -64,6 +65,17 @@ export interface MothershipChatHistory {
6465

6566
export type MothershipChatOwner = string | { organizationId: string }
6667

68+
/** Reports which chats were deleted when another request in the batch failed. */
69+
export class MothershipChatDeleteError extends Error {
70+
constructor(
71+
readonly deletedChatIds: string[],
72+
cause: Error
73+
) {
74+
super(cause.message, { cause })
75+
this.name = 'MothershipChatDeleteError'
76+
}
77+
}
78+
6779
export const mothershipChatKeys = {
6880
all: ['mothership-chats'] as const,
6981
lists: () => [...mothershipChatKeys.all, 'list'] as const,
@@ -377,7 +389,10 @@ export function useDeleteMothershipChats(owner?: MothershipChatOwner) {
377389
})
378390
)
379391
const failed = results.find((result) => result.status === 'rejected')
380-
if (failed) throw failed.reason
392+
if (failed) {
393+
const deletedChatIds = chatIds.filter((_, index) => results[index].status === 'fulfilled')
394+
throw new MothershipChatDeleteError(deletedChatIds, toError(failed.reason))
395+
}
381396
},
382397
onSettled: () => {
383398
queryClient.invalidateQueries({ queryKey: mothershipChatKeys.ownerLists(owner) })

0 commit comments

Comments
 (0)