fix(settings): preserve tab when switching workspaces (#6704)

This commit is contained in:
Theodore Li
2026-08-14 14:17:05 -07:00
committed by GitHub
parent af076a7a34
commit 3d4e3d26dd
2 changed files with 84 additions and 4 deletions
@@ -20,6 +20,7 @@ const {
}))
vi.mock('next/navigation', () => ({
usePathname: () => '/workspace/workspace-denied',
useRouter: () => ({ push: mockPush }),
}))
@@ -49,7 +50,52 @@ vi.mock('@/stores/workflows/registry/store', () => ({
) => selector({ switchToWorkspace: mockSwitchToWorkspace }),
}))
import { useWorkspaceManagement } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workspace-management'
import {
resolveWorkspaceSwitchHref,
useWorkspaceManagement,
} from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workspace-management'
describe('resolveWorkspaceSwitchHref', () => {
it('preserves the active settings section', () => {
expect(
resolveWorkspaceSwitchHref({
pathname: '/workspace/workspace-a/settings/mcp',
currentWorkspaceId: 'workspace-a',
targetWorkspaceId: 'workspace-b',
})
).toBe('/workspace/workspace-b/settings/mcp')
})
it('drops workspace-scoped settings detail segments', () => {
expect(
resolveWorkspaceSwitchHref({
pathname: '/workspace/workspace-a/settings/secrets/credential-a',
currentWorkspaceId: 'workspace-a',
targetWorkspaceId: 'workspace-b',
})
).toBe('/workspace/workspace-b/settings/secrets')
})
it('navigates to the workspace root outside settings', () => {
expect(
resolveWorkspaceSwitchHref({
pathname: '/workspace/workspace-a/w/workflow-a',
currentWorkspaceId: 'workspace-a',
targetWorkspaceId: 'workspace-b',
})
).toBe('/workspace/workspace-b')
})
it('fails fast when a settings pathname has no section', () => {
expect(() =>
resolveWorkspaceSwitchHref({
pathname: '/workspace/workspace-a/settings/',
currentWorkspaceId: 'workspace-a',
targetWorkspaceId: 'workspace-b',
})
).toThrow('Settings pathname is missing a section')
})
})
function Harness() {
useWorkspaceManagement({ workspaceId: 'workspace-denied', sessionUserId: 'user-1' })
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { useRouter } from 'next/navigation'
import { usePathname, useRouter } from 'next/navigation'
import { requestJson } from '@/lib/api/client/request'
import { updateUserSettingsContract } from '@/lib/api/contracts'
import { WorkspaceRecencyStorage } from '@/lib/core/utils/browser-storage'
@@ -25,6 +25,33 @@ interface UseWorkspaceManagementProps {
sessionUserId?: string
}
interface ResolveWorkspaceSwitchHrefParams {
pathname: string
currentWorkspaceId: string
targetWorkspaceId: string
}
/**
* Keeps the active settings section across workspace switches without carrying
* workspace-scoped detail IDs into the destination workspace.
*/
export function resolveWorkspaceSwitchHref({
pathname,
currentWorkspaceId,
targetWorkspaceId,
}: ResolveWorkspaceSwitchHrefParams): string {
const targetWorkspaceHref = `/workspace/${targetWorkspaceId}`
const settingsPrefix = `/workspace/${currentWorkspaceId}/settings/`
if (!pathname.startsWith(settingsPrefix)) return targetWorkspaceHref
const [section] = pathname.slice(settingsPrefix.length).split('/')
if (!section) {
throw new Error(`Settings pathname is missing a section: ${pathname}`)
}
return `${targetWorkspaceHref}/settings/${section}`
}
/**
* Manages workspace operations including fetching, switching, creating, deleting, and leaving workspaces.
* Handles URL synchronization and recency-based ordering. Route access is
@@ -40,6 +67,7 @@ export function useWorkspaceManagement({
sessionUserId,
}: UseWorkspaceManagementProps) {
const router = useRouter()
const pathname = usePathname()
const switchToWorkspace = useWorkflowRegistry((state) => state.switchToWorkspace)
const { data: workspaces = [], isLoading: isWorkspacesLoading } = useWorkspacesQuery(
@@ -157,15 +185,21 @@ export function useWorkspaceManagement({
return
}
const href = resolveWorkspaceSwitchHref({
pathname,
currentWorkspaceId: workspaceIdRef.current,
targetWorkspaceId: workspace.id,
})
try {
switchToWorkspace(workspace.id)
routerRef.current?.push(`/workspace/${workspace.id}`)
routerRef.current.push(href)
logger.info(`Switched to workspace: ${workspace.name} (${workspace.id})`)
} catch (error) {
logger.error('Error switching workspace:', error)
}
},
[switchToWorkspace]
[pathname, switchToWorkspace]
)
const handleCreateWorkspace = useCallback(