fix: remove workspace switching confirmation dialog (#9250)

## Summary

Remove the workspace switching confirmation dialog since switching
workspaces no longer discards unsaved changes.

## Changes

- **What**: Remove `hasUnsavedChanges` check, `dialogService.confirm`
call, and unused imports (`useI18n`, `useWorkflowStore`,
`useDialogService`) from `useWorkspaceSwitch`. Rename
`switchWithConfirmation` to `switchWorkspace`. Update callers
(`WorkspaceSwitcherPopover.vue`, `InviteAcceptedToast.vue`). Remove
`workspace.unsavedChanges` i18n entries from all 12 locale files.
Simplify tests to cover core switching behavior only.

## Review Focus

The confirmation dialog was showing inaccurate information (warning
about discarding unsaved changes when that no longer happens). This is a
pure removal with no new behavior.

<!-- Pipeline-Ticket: COM-15441 -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9250-fix-remove-workspace-switching-confirmation-dialog-3136d73d365081d3b959da22e8f151d1)
by [Unito](https://www.unito.io)
This commit is contained in:
Christian Byrne
2026-03-07 14:19:05 -08:00
committed by GitHub
parent ec129de63d
commit 2875f897dc
16 changed files with 26 additions and 181 deletions

View File

@@ -3373,10 +3373,6 @@
"addedToWorkspace": "تمت إضافتك إلى {workspaceName}",
"inviteAccepted": "تم قبول الدعوة",
"inviteFailed": "فشل في قبول الدعوة",
"unsavedChanges": {
"message": "لديك تغييرات غير محفوظة. هل تريد تجاهلها والانتقال إلى مساحة عمل أخرى؟",
"title": "تغييرات غير محفوظة"
},
"viewWorkspace": "عرض مساحة العمل"
},
"workspaceAuth": {

View File

@@ -3405,14 +3405,11 @@
"retryDownload": "Retry download"
},
"workspace": {
"unsavedChanges": {
"title": "Unsaved Changes",
"message": "You have unsaved changes. Do you want to discard them and switch workspaces?"
},
"inviteAccepted": "Invite Accepted",
"addedToWorkspace": "You have been added to:",
"inviteFailed": "Failed to Accept Invite",
"viewWorkspace": "View workspace"
"viewWorkspace": "View workspace",
"switchFailed": "Failed to switch workspace. Please try again."
},
"workspaceAuth": {
"errors": {

View File

@@ -3373,10 +3373,6 @@
"addedToWorkspace": "Has sido añadido a {workspaceName}",
"inviteAccepted": "Invitación aceptada",
"inviteFailed": "No se pudo aceptar la invitación",
"unsavedChanges": {
"message": "Tienes cambios no guardados. ¿Quieres descartarlos y cambiar de espacio de trabajo?",
"title": "Cambios no guardados"
},
"viewWorkspace": "Ver espacio de trabajo"
},
"workspaceAuth": {

View File

@@ -3385,10 +3385,6 @@
"addedToWorkspace": "شما به {workspaceName} اضافه شدید",
"inviteAccepted": "دعوت پذیرفته شد",
"inviteFailed": "پذیرش دعوت ناموفق بود",
"unsavedChanges": {
"message": "شما تغییرات ذخیره‌نشده دارید. آیا می‌خواهید آن‌ها را رها کرده و فضای کاری را تغییر دهید؟",
"title": "تغییرات ذخیره‌نشده"
},
"viewWorkspace": "مشاهده workspace"
},
"workspaceAuth": {

View File

@@ -3373,10 +3373,6 @@
"addedToWorkspace": "Vous avez été ajouté à {workspaceName}",
"inviteAccepted": "Invitation acceptée",
"inviteFailed": "Échec de l'acceptation de l'invitation",
"unsavedChanges": {
"message": "Vous avez des modifications non enregistrées. Voulez-vous les abandonner et changer despace de travail ?",
"title": "Modifications non enregistrées"
},
"viewWorkspace": "Voir lespace de travail"
},
"workspaceAuth": {

View File

@@ -3373,10 +3373,6 @@
"addedToWorkspace": "{workspaceName}に追加されました",
"inviteAccepted": "招待を承諾しました",
"inviteFailed": "招待の承諾に失敗しました",
"unsavedChanges": {
"message": "未保存の変更があります。破棄してワークスペースを切り替えますか?",
"title": "未保存の変更"
},
"viewWorkspace": "ワークスペースを見る"
},
"workspaceAuth": {

View File

@@ -3373,10 +3373,6 @@
"addedToWorkspace": "{workspaceName} 워크스페이스에 추가되었습니다",
"inviteAccepted": "초대 수락됨",
"inviteFailed": "초대 수락에 실패했습니다",
"unsavedChanges": {
"message": "저장되지 않은 변경 사항이 있습니다. 변경 사항을 취소하고 워크스페이스를 전환하시겠습니까?",
"title": "저장되지 않은 변경 사항"
},
"viewWorkspace": "워크스페이스 보기"
},
"workspaceAuth": {

View File

@@ -3385,10 +3385,6 @@
"addedToWorkspace": "Você foi adicionado ao {workspaceName}",
"inviteAccepted": "Convite aceito",
"inviteFailed": "Falha ao aceitar convite",
"unsavedChanges": {
"message": "Você tem alterações não salvas. Deseja descartá-las e trocar de espaço de trabalho?",
"title": "Alterações não salvas"
},
"viewWorkspace": "Ver workspace"
},
"workspaceAuth": {

View File

@@ -3373,10 +3373,6 @@
"addedToWorkspace": "Вы были добавлены в {workspaceName}",
"inviteAccepted": "Приглашение принято",
"inviteFailed": "Не удалось принять приглашение",
"unsavedChanges": {
"message": "У вас есть несохранённые изменения. Хотите их отменить и переключиться на другое рабочее пространство?",
"title": "Несохранённые изменения"
},
"viewWorkspace": "Просмотреть рабочее пространство"
},
"workspaceAuth": {

View File

@@ -3373,10 +3373,6 @@
"addedToWorkspace": "{workspaceName} çalışma alanına eklendiniz",
"inviteAccepted": "Davet kabul edildi",
"inviteFailed": "Davet kabul edilemedi",
"unsavedChanges": {
"message": "Kaydedilmemiş değişiklikleriniz var. Bunları iptal edip çalışma alanlarını değiştirmek istiyor musunuz?",
"title": "Kaydedilmemiş Değişiklikler"
},
"viewWorkspace": "Çalışma alanını görüntüle"
},
"workspaceAuth": {

View File

@@ -3373,10 +3373,6 @@
"addedToWorkspace": "你已被加入 {workspaceName}",
"inviteAccepted": "已接受邀請",
"inviteFailed": "接受邀請失敗",
"unsavedChanges": {
"message": "您有未儲存的變更。是否要捨棄這些變更並切換工作區?",
"title": "未儲存的變更"
},
"viewWorkspace": "檢視工作區"
},
"workspaceAuth": {

View File

@@ -3385,10 +3385,6 @@
"addedToWorkspace": "您已被加入 {workspaceName}",
"inviteAccepted": "邀请已接受",
"inviteFailed": "接受邀请失败",
"unsavedChanges": {
"message": "您有未保存的更改。是否要放弃这些更改并切换工作区?",
"title": "未保存的更改"
},
"viewWorkspace": "查看工作区"
},
"workspaceAuth": {

View File

@@ -139,7 +139,7 @@ const emit = defineEmits<{
}>()
const { t } = useI18n()
const { switchWithConfirmation } = useWorkspaceSwitch()
const { switchWorkspace } = useWorkspaceSwitch()
const { subscription } = useBillingContext()
const tierKeyMap: Record<string, string> = {
@@ -226,7 +226,7 @@ function getTierLabel(workspace: AvailableWorkspace): string | null {
}
async function handleSelectWorkspace(workspace: AvailableWorkspace) {
const success = await switchWithConfirmation(workspace.id)
const success = await switchWorkspace(workspace.id)
if (success) {
emit('select', workspace)
}

View File

@@ -33,10 +33,18 @@ import { useWorkspaceSwitch } from '@/platform/workspace/composables/useWorkspac
const { t } = useI18n()
const toast = useToast()
const { switchWithConfirmation } = useWorkspaceSwitch()
const { switchWorkspace } = useWorkspaceSwitch()
function viewWorkspace(workspaceId: string) {
void switchWithConfirmation(workspaceId)
toast.removeGroup('invite-accepted')
async function viewWorkspace(workspaceId: string) {
const success = await switchWorkspace(workspaceId)
if (success) {
toast.removeGroup('invite-accepted')
} else {
toast.add({
severity: 'error',
summary: t('workspace.switchFailed'),
life: 5000
})
}
}
</script>

View File

@@ -20,32 +20,6 @@ vi.mock('pinia', () => ({
})
}))
const mockModifiedWorkflows = vi.hoisted(
() => [] as Array<{ isModified: boolean }>
)
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
useWorkflowStore: () => ({
get modifiedWorkflows() {
return mockModifiedWorkflows
}
})
}))
const mockConfirm = vi.hoisted(() => vi.fn())
vi.mock('@/services/dialogService', () => ({
useDialogService: () => ({
confirm: mockConfirm
})
}))
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key: string) => key
})
}))
describe('useWorkspaceSwitch', () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -57,103 +31,37 @@ describe('useWorkspaceSwitch', () => {
created_at: '2026-01-01T00:00:00Z',
joined_at: '2026-01-01T00:00:00Z'
}
mockModifiedWorkflows.length = 0
})
afterEach(() => {
vi.unstubAllGlobals()
})
describe('hasUnsavedChanges', () => {
it('returns true when there are modified workflows', () => {
mockModifiedWorkflows.push({ isModified: true })
const { hasUnsavedChanges } = useWorkspaceSwitch()
expect(hasUnsavedChanges()).toBe(true)
})
it('returns true when multiple workflows are modified', () => {
mockModifiedWorkflows.push({ isModified: true }, { isModified: true })
const { hasUnsavedChanges } = useWorkspaceSwitch()
expect(hasUnsavedChanges()).toBe(true)
})
it('returns false when no workflows are modified', () => {
mockModifiedWorkflows.length = 0
const { hasUnsavedChanges } = useWorkspaceSwitch()
expect(hasUnsavedChanges()).toBe(false)
})
})
describe('switchWithConfirmation', () => {
describe('switchWorkspace', () => {
it('returns true immediately if switching to the same workspace', async () => {
const { switchWithConfirmation } = useWorkspaceSwitch()
const { switchWorkspace } = useWorkspaceSwitch()
const result = await switchWithConfirmation('workspace-1')
const result = await switchWorkspace('workspace-1')
expect(result).toBe(true)
expect(mockSwitchWorkspace).not.toHaveBeenCalled()
expect(mockConfirm).not.toHaveBeenCalled()
})
it('switches directly without dialog when no unsaved changes', async () => {
mockModifiedWorkflows.length = 0
it('switches directly to the new workspace', async () => {
mockSwitchWorkspace.mockResolvedValue(undefined)
const { switchWithConfirmation } = useWorkspaceSwitch()
const { switchWorkspace } = useWorkspaceSwitch()
const result = await switchWithConfirmation('workspace-2')
expect(result).toBe(true)
expect(mockConfirm).not.toHaveBeenCalled()
expect(mockSwitchWorkspace).toHaveBeenCalledWith('workspace-2')
})
it('shows confirmation dialog when there are unsaved changes', async () => {
mockModifiedWorkflows.push({ isModified: true })
mockConfirm.mockResolvedValue(true)
mockSwitchWorkspace.mockResolvedValue(undefined)
const { switchWithConfirmation } = useWorkspaceSwitch()
await switchWithConfirmation('workspace-2')
expect(mockConfirm).toHaveBeenCalledWith({
title: 'workspace.unsavedChanges.title',
message: 'workspace.unsavedChanges.message',
type: 'dirtyClose'
})
})
it('returns false if user cancels the confirmation dialog', async () => {
mockModifiedWorkflows.push({ isModified: true })
mockConfirm.mockResolvedValue(false)
const { switchWithConfirmation } = useWorkspaceSwitch()
const result = await switchWithConfirmation('workspace-2')
expect(result).toBe(false)
expect(mockSwitchWorkspace).not.toHaveBeenCalled()
})
it('calls switchWorkspace after user confirms', async () => {
mockModifiedWorkflows.push({ isModified: true })
mockConfirm.mockResolvedValue(true)
mockSwitchWorkspace.mockResolvedValue(undefined)
const { switchWithConfirmation } = useWorkspaceSwitch()
const result = await switchWithConfirmation('workspace-2')
const result = await switchWorkspace('workspace-2')
expect(result).toBe(true)
expect(mockSwitchWorkspace).toHaveBeenCalledWith('workspace-2')
})
it('returns false if switchWorkspace throws an error', async () => {
mockModifiedWorkflows.length = 0
mockSwitchWorkspace.mockRejectedValue(new Error('Switch failed'))
const { switchWithConfirmation } = useWorkspaceSwitch()
const { switchWorkspace } = useWorkspaceSwitch()
const result = await switchWithConfirmation('workspace-2')
const result = await switchWorkspace('workspace-2')
expect(result).toBe(false)
})

View File

@@ -1,41 +1,18 @@
import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useTeamWorkspaceStore } from '@/platform/workspace/stores/teamWorkspaceStore'
import { useDialogService } from '@/services/dialogService'
export function useWorkspaceSwitch() {
const { t } = useI18n()
const workspaceStore = useTeamWorkspaceStore()
const { activeWorkspace } = storeToRefs(workspaceStore)
const workflowStore = useWorkflowStore()
const dialogService = useDialogService()
function hasUnsavedChanges(): boolean {
return workflowStore.modifiedWorkflows.length > 0
}
async function switchWithConfirmation(workspaceId: string): Promise<boolean> {
async function switchWorkspace(workspaceId: string): Promise<boolean> {
if (activeWorkspace.value?.id === workspaceId) {
return true
}
if (hasUnsavedChanges()) {
const confirmed = await dialogService.confirm({
title: t('workspace.unsavedChanges.title'),
message: t('workspace.unsavedChanges.message'),
type: 'dirtyClose'
})
if (!confirmed) {
return false
}
}
try {
await workspaceStore.switchWorkspace(workspaceId)
// Note: switchWorkspace triggers page reload internally
return true
} catch {
return false
@@ -43,7 +20,6 @@ export function useWorkspaceSwitch() {
}
return {
hasUnsavedChanges,
switchWithConfirmation
switchWorkspace
}
}