From c9d7445531bc0d8bd7cfd67a3fef184efafeb0e6 Mon Sep 17 00:00:00 2001 From: Alex Netsch Date: Fri, 3 Apr 2026 17:20:31 +0100 Subject: [PATCH] Align workspace session invalidation with host-scoped query keys (Vibe Kanban) (#3319) * fix: include hostId in session cache invalidation keys to match query key The workspaceSessions query key includes hostId as the second element, but all four invalidation sites omitted it, causing React Query prefix matching to fail silently. This left stale cache after session creation, making the sessions dropdown appear empty until page refresh. Co-Authored-By: Claude Opus 4.6 (1M context) * refactor: centralize workspaceSessions query key into workspaceSessionKeys helper Follows the existing pattern (workspaceSummaryKeys, workspaceRecordKeys, etc.) so the key shape is defined in one place and invalidation sites can't drift. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .../workspace-chat/model/hooks/useCreateSession.ts | 8 +++++++- .../workspace-chat/ui/SessionChatBoxContainer.tsx | 7 +++++-- .../dialogs/command-bar/StartReviewDialog.tsx | 6 +++++- .../shared/dialogs/tasks/ResolveConflictsDialog.tsx | 6 +++++- .../src/shared/hooks/useWorkspaceSessions.ts | 3 ++- .../src/shared/hooks/workspaceSessionKeys.ts | 13 +++++++++++++ 6 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 packages/web-core/src/shared/hooks/workspaceSessionKeys.ts diff --git a/packages/web-core/src/features/workspace-chat/model/hooks/useCreateSession.ts b/packages/web-core/src/features/workspace-chat/model/hooks/useCreateSession.ts index 1ea689e9..f97a4a23 100644 --- a/packages/web-core/src/features/workspace-chat/model/hooks/useCreateSession.ts +++ b/packages/web-core/src/features/workspace-chat/model/hooks/useCreateSession.ts @@ -1,5 +1,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { sessionsApi } from '@/shared/lib/api'; +import { useHostId } from '@/shared/providers/HostIdProvider'; +import { workspaceSessionKeys } from '@/shared/hooks/workspaceSessionKeys'; import type { Session, CreateFollowUpAttempt, @@ -18,6 +20,7 @@ interface CreateSessionParams { */ export function useCreateSession() { const queryClient = useQueryClient(); + const hostId = useHostId(); return useMutation({ mutationFn: async ({ @@ -43,7 +46,10 @@ export function useCreateSession() { onSuccess: (session) => { // Invalidate session queries to refresh the list queryClient.invalidateQueries({ - queryKey: ['workspaceSessions', session.workspace_id], + queryKey: workspaceSessionKeys.byWorkspace( + session.workspace_id, + hostId + ), }); }, }); diff --git a/packages/web-core/src/features/workspace-chat/ui/SessionChatBoxContainer.tsx b/packages/web-core/src/features/workspace-chat/ui/SessionChatBoxContainer.tsx index 14478b2e..ff8be730 100644 --- a/packages/web-core/src/features/workspace-chat/ui/SessionChatBoxContainer.tsx +++ b/packages/web-core/src/features/workspace-chat/ui/SessionChatBoxContainer.tsx @@ -9,6 +9,8 @@ import { ExecutionProcessStatus, } from 'shared/types'; import { AgentIcon } from '@/shared/components/AgentIcon'; +import { useHostId } from '@/shared/providers/HostIdProvider'; +import { workspaceSessionKeys } from '@/shared/hooks/workspaceSessionKeys'; import { useWorkspaceExecution } from '@/shared/hooks/useWorkspaceExecution'; import { useWorkspaceRepo } from '@/shared/hooks/useWorkspaceRepo'; import { useUserSystem } from '@/shared/hooks/useUserSystem'; @@ -169,6 +171,7 @@ export function SessionChatBoxContainer(props: SessionChatBoxContainerProps) { const sessionId = session?.id; const queryClient = useQueryClient(); + const hostId = useHostId(); const handleRenameSession = useCallback( (targetSessionId: string, currentName: string) => { @@ -177,12 +180,12 @@ export function SessionChatBoxContainer(props: SessionChatBoxContainerProps) { onRename: async (newName: string) => { await sessionsApi.update(targetSessionId, { name: newName }); void queryClient.invalidateQueries({ - queryKey: ['workspaceSessions', workspaceId], + queryKey: workspaceSessionKeys.byWorkspace(workspaceId, hostId), }); }, }); }, - [queryClient, workspaceId] + [queryClient, hostId, workspaceId] ); const appNavigation = useAppNavigation(); diff --git a/packages/web-core/src/shared/dialogs/command-bar/StartReviewDialog.tsx b/packages/web-core/src/shared/dialogs/command-bar/StartReviewDialog.tsx index 11fe89c4..204d89cd 100644 --- a/packages/web-core/src/shared/dialogs/command-bar/StartReviewDialog.tsx +++ b/packages/web-core/src/shared/dialogs/command-bar/StartReviewDialog.tsx @@ -17,6 +17,8 @@ import { AgentSelector } from '@/shared/components/tasks/AgentSelector'; import { ConfigSelector } from '@/shared/components/tasks/ConfigSelector'; import { useUserSystem } from '@/shared/hooks/useUserSystem'; import { useWorkspaceContext } from '@/shared/hooks/useWorkspaceContext'; +import { useHostId } from '@/shared/providers/HostIdProvider'; +import { workspaceSessionKeys } from '@/shared/hooks/workspaceSessionKeys'; import { sessionsApi } from '@/shared/lib/api'; import { useQueryClient } from '@tanstack/react-query'; import { create, useModal } from '@ebay/nice-modal-react'; @@ -35,6 +37,7 @@ const StartReviewDialogImpl = create( ({ sessionId, workspaceId, reviewMarkdown, defaultProfile, onSuccess }) => { const modal = useModal(); const queryClient = useQueryClient(); + const hostId = useHostId(); const { profiles, config } = useUserSystem(); const { sessions, selectedSession, selectedSessionId, selectSession } = useWorkspaceContext(); @@ -95,7 +98,7 @@ const StartReviewDialogImpl = create( targetSessionId = session.id; queryClient.invalidateQueries({ - queryKey: ['workspaceSessions', workspaceId], + queryKey: workspaceSessionKeys.byWorkspace(workspaceId, hostId), }); } @@ -140,6 +143,7 @@ const StartReviewDialogImpl = create( effectiveProfile, resolvedSessionId, workspaceId, + hostId, createNewSession, includeGitContext, reviewMarkdown, diff --git a/packages/web-core/src/shared/dialogs/tasks/ResolveConflictsDialog.tsx b/packages/web-core/src/shared/dialogs/tasks/ResolveConflictsDialog.tsx index ce99f0a7..eb631734 100644 --- a/packages/web-core/src/shared/dialogs/tasks/ResolveConflictsDialog.tsx +++ b/packages/web-core/src/shared/dialogs/tasks/ResolveConflictsDialog.tsx @@ -15,6 +15,8 @@ import { AgentSelector } from '@/shared/components/tasks/AgentSelector'; import { ConfigSelector } from '@/shared/components/tasks/ConfigSelector'; import { useUserSystem } from '@/shared/hooks/useUserSystem'; import { useWorkspaceContext } from '@/shared/hooks/useWorkspaceContext'; +import { useHostId } from '@/shared/providers/HostIdProvider'; +import { workspaceSessionKeys } from '@/shared/hooks/workspaceSessionKeys'; import { sessionsApi } from '@/shared/lib/api'; import { useQueryClient } from '@tanstack/react-query'; import { create, useModal } from '@ebay/nice-modal-react'; @@ -52,6 +54,7 @@ const ResolveConflictsDialogImpl = create( }) => { const modal = useModal(); const queryClient = useQueryClient(); + const hostId = useHostId(); const { profiles, config } = useUserSystem(); const { workspaceId: activeWorkspaceId, @@ -176,7 +179,7 @@ const ResolveConflictsDialogImpl = create( // Invalidate queries and wait for them to complete await Promise.all([ queryClient.invalidateQueries({ - queryKey: ['workspaceSessions', workspaceId], + queryKey: workspaceSessionKeys.byWorkspace(workspaceId, hostId), }), queryClient.invalidateQueries({ queryKey: ['processes', workspaceId], @@ -208,6 +211,7 @@ const ResolveConflictsDialogImpl = create( selectedSessionId, createNewSession, workspaceId, + hostId, conflictInstructions, queryClient, selectSession, diff --git a/packages/web-core/src/shared/hooks/useWorkspaceSessions.ts b/packages/web-core/src/shared/hooks/useWorkspaceSessions.ts index 002747cb..8cf249b3 100644 --- a/packages/web-core/src/shared/hooks/useWorkspaceSessions.ts +++ b/packages/web-core/src/shared/hooks/useWorkspaceSessions.ts @@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query'; import { useState, useCallback, useEffect, useMemo, useRef } from 'react'; import { sessionsApi } from '@/shared/lib/api'; import { useHostId } from '@/shared/providers/HostIdProvider'; +import { workspaceSessionKeys } from '@/shared/hooks/workspaceSessionKeys'; import type { Session } from 'shared/types'; interface UseWorkspaceSessionsOptions { @@ -43,7 +44,7 @@ export function useWorkspaceSessions( const prevWorkspaceIdRef = useRef(workspaceId); const { data: sessions = [], isLoading } = useQuery({ - queryKey: ['workspaceSessions', hostId ?? 'local', workspaceId], + queryKey: workspaceSessionKeys.byWorkspace(workspaceId, hostId), queryFn: () => sessionsApi.getByWorkspace(workspaceId!), enabled: enabled && !!workspaceId, }); diff --git a/packages/web-core/src/shared/hooks/workspaceSessionKeys.ts b/packages/web-core/src/shared/hooks/workspaceSessionKeys.ts new file mode 100644 index 00000000..d2fcfd10 --- /dev/null +++ b/packages/web-core/src/shared/hooks/workspaceSessionKeys.ts @@ -0,0 +1,13 @@ +import { getHostRequestScopeQueryKey } from '@/shared/lib/hostRequestScope'; + +export const workspaceSessionKeys = { + byWorkspace: ( + workspaceId: string | undefined, + hostId: string | null = null + ) => + [ + 'workspaceSessions', + getHostRequestScopeQueryKey(hostId), + workspaceId, + ] as const, +};