Skip to content

Commit d65bfd9

Browse files
fazxesclaude
andcommitted
feat(conversations): scope conversations to workspace like Claude Code
Add workspace_path field to conversations so each workspace/folder shows only its own conversations in the sidebar, matching Claude Code behavior. Changes: - Add workspace_path to ConversationSummary and Conversation structs - Update Tauri commands to accept/filter by workspace_path - Add useWorkspaceConversations hook with useMemo for filtered list - Fix sidebar "New conversation" button to include workspace_path - Add cleanup_orphaned_conversations for migration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 300611f commit d65bfd9

9 files changed

Lines changed: 164 additions & 37 deletions

File tree

‎apps/agent/src/components/layout/primary-sidebar.tsx‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import {
2323
useUIStore,
2424
useIsLeftSidebarCollapsed,
2525
useWorkspaceName,
26-
useConversations,
26+
useWorkspacePath,
27+
useWorkspaceConversations,
2728
useActiveConversationId,
2829
} from '@/stores/ui-store';
2930

@@ -90,7 +91,8 @@ export const PrimarySidebar: FC<PrimarySidebarProps> = ({ width }) => {
9091
} = useUIStore();
9192
const isCollapsed = useIsLeftSidebarCollapsed();
9293
const workspaceName = useWorkspaceName();
93-
const conversations = useConversations();
94+
const workspacePath = useWorkspacePath();
95+
const conversations = useWorkspaceConversations();
9496
const activeConversationId = useActiveConversationId();
9597
const { postMessage } = useTauri();
9698
const [workspaceExpanded, setWorkspaceExpanded] = useState(true);
@@ -106,8 +108,9 @@ export const PrimarySidebar: FC<PrimarySidebarProps> = ({ width }) => {
106108
type: 'conversation:create',
107109
uuid: crypto.randomUUID(),
108110
title: 'Untitled',
111+
workspace_path: workspacePath ?? undefined,
109112
});
110-
}, [conversations, activeConversationId, postMessage]);
113+
}, [conversations, activeConversationId, workspacePath, postMessage]);
111114

112115
const handleLoadConversation = useCallback(
113116
(sessionId: string): void => {

‎apps/agent/src/hooks/use-chat-messages.ts‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export function useChatMessages(options: UseChatMessagesOptions = {}): UseChatMe
9898
addConversation,
9999
updateConversationTitle,
100100
conversations,
101+
workspacePath,
101102
} = useUIStore();
102103
const {
103104
setInputMode,
@@ -338,6 +339,7 @@ export function useChatMessages(options: UseChatMessagesOptions = {}): UseChatMe
338339
title: message.title,
339340
updatedAt: Date.now(),
340341
messageCount: 0,
342+
workspacePath: message.workspace_path,
341343
});
342344
setMessages([]);
343345
switchSession(message.session_id); // Switch to new session (resets usage for new conversation)
@@ -355,6 +357,7 @@ export function useChatMessages(options: UseChatMessagesOptions = {}): UseChatMe
355357
title: c.title,
356358
updatedAt: c.updated_at,
357359
messageCount: c.message_count,
360+
workspacePath: c.workspace_path,
358361
}))
359362
);
360363
}
@@ -610,15 +613,16 @@ export function useChatMessages(options: UseChatMessagesOptions = {}): UseChatMe
610613

611614
const { postMessage, isMockMode } = useTauri({ onMessage: handleMessage });
612615

613-
// Request conversation list when session is ready
616+
// Request conversation list when session is ready or workspace changes
614617
useEffect(() => {
615-
if (sessionId) {
618+
if (sessionId || workspacePath) {
616619
postMessage({
617620
type: 'conversation:list',
618621
uuid: crypto.randomUUID(),
622+
workspace_path: workspacePath ?? undefined,
619623
});
620624
}
621-
}, [sessionId, postMessage]);
625+
}, [sessionId, workspacePath, postMessage]);
622626

623627
// Request file list for @ mentions
624628
useEffect(() => {
@@ -740,6 +744,7 @@ export function useChatMessages(options: UseChatMessagesOptions = {}): UseChatMe
740744
type: 'conversation:create',
741745
uuid: crypto.randomUUID(),
742746
title: text,
747+
workspace_path: workspacePath ?? undefined,
743748
});
744749
return;
745750
}
@@ -819,6 +824,7 @@ export function useChatMessages(options: UseChatMessagesOptions = {}): UseChatMe
819824
isAgentRunning,
820825
messages.length,
821826
conversations,
827+
workspacePath,
822828
postMessage,
823829
updateConversationTitle,
824830
storeQueueMessage,

‎apps/agent/src/hooks/use-tauri.ts‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,13 +530,15 @@ async function handleTauriMessage(message: WebviewMessage): Promise<void> {
530530
try {
531531
const sessionId = crypto.randomUUID();
532532
const title = message.title ?? 'New Conversation';
533-
await conversationCreate(sessionId, title);
533+
const workspacePath = message.workspace_path;
534+
await conversationCreate(sessionId, title, workspacePath);
534535
window.postMessage(
535536
{
536537
type: 'conversation:created',
537538
uuid: crypto.randomUUID(),
538539
session_id: sessionId,
539540
title,
541+
workspace_path: workspacePath,
540542
},
541543
'*'
542544
);
@@ -550,6 +552,7 @@ async function handleTauriMessage(message: WebviewMessage): Promise<void> {
550552
uuid: crypto.randomUUID(),
551553
session_id: sessionId,
552554
title: message.title ?? 'New Conversation',
555+
workspace_path: message.workspace_path,
553556
},
554557
'*'
555558
);
@@ -560,7 +563,8 @@ async function handleTauriMessage(message: WebviewMessage): Promise<void> {
560563
// Handle conversation list request
561564
if (message.type === 'conversation:list') {
562565
try {
563-
const conversations = await conversationList();
566+
const workspacePath = message.workspace_path;
567+
const conversations = await conversationList(workspacePath);
564568
window.postMessage(
565569
{
566570
type: 'conversation:list',
@@ -570,6 +574,7 @@ async function handleTauriMessage(message: WebviewMessage): Promise<void> {
570574
title: c.title,
571575
updated_at: c.updatedAt,
572576
message_count: c.messageCount,
577+
workspace_path: c.workspacePath,
573578
})),
574579
},
575580
'*'

‎apps/agent/src/lib/backend.ts‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -840,17 +840,19 @@ export interface ConversationSummaryDto {
840840
title: string;
841841
updatedAt: number;
842842
messageCount: number;
843+
workspacePath?: string;
843844
}
844845

845846
export async function conversationCreate(
846847
sessionId: string,
847-
title: string
848+
title: string,
849+
workspacePath?: string
848850
): Promise<ConversationDto> {
849-
return invoke<ConversationDto>('conversation_create', { sessionId, title });
851+
return invoke<ConversationDto>('conversation_create', { sessionId, title, workspacePath });
850852
}
851853

852-
export async function conversationList(): Promise<ConversationSummaryDto[]> {
853-
return invoke<ConversationSummaryDto[]>('conversation_list');
854+
export async function conversationList(workspacePath?: string): Promise<ConversationSummaryDto[]> {
855+
return invoke<ConversationSummaryDto[]>('conversation_list', { workspacePath });
854856
}
855857

856858
export async function conversationLoad(sessionId: string): Promise<ConversationDto | null> {
@@ -867,9 +869,10 @@ export async function conversationUpdateTitle(sessionId: string, title: string):
867869

868870
export async function conversationAddMessage(
869871
sessionId: string,
870-
message: ConversationMessageDto
872+
message: ConversationMessageDto,
873+
workspacePath?: string
871874
): Promise<void> {
872-
return invoke('conversation_add_message', { sessionId, message });
875+
return invoke('conversation_add_message', { sessionId, message, workspacePath });
873876
}
874877

875878
export async function conversationFork(
@@ -888,6 +891,10 @@ export async function conversationDataPath(): Promise<string> {
888891
return invoke<string>('conversation_data_path');
889892
}
890893

894+
export async function conversationCleanupOrphaned(): Promise<number> {
895+
return invoke<number>('conversation_cleanup_orphaned');
896+
}
897+
891898
// ============================================
892899
// AI Operations
893900
// ============================================

‎apps/agent/src/stores/ui-store.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useMemo } from 'react';
12
import { create } from 'zustand';
23
import { immer } from 'zustand/middleware/immer';
34

@@ -388,6 +389,22 @@ export const useConversations = (): ConversationSummary[] => {
388389
return useUIStore((state) => state.conversations);
389390
};
390391

392+
export const useWorkspaceConversations = (): ConversationSummary[] => {
393+
// Get raw state values with stable selectors
394+
const workspacePath = useUIStore((state) => state.workspacePath);
395+
const conversations = useUIStore((state) => state.conversations);
396+
397+
// Memoize the filtered result to prevent unnecessary re-renders
398+
return useMemo(() => {
399+
if (!workspacePath) {
400+
// No workspace set - return empty list (user should open a folder first)
401+
return [];
402+
}
403+
// Filter to only show conversations for the current workspace
404+
return conversations.filter((c) => c.workspacePath === workspacePath);
405+
}, [workspacePath, conversations]);
406+
};
407+
391408
export const useTerminalPosition = (): TerminalPosition => {
392409
return useUIStore((state) => state.terminalPosition);
393410
};

‎apps/agent/src/types/protocol.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export const StoredConversationSummarySchema = z
126126
title: z.string(),
127127
updatedAt: z.number(),
128128
messageCount: z.number(),
129+
workspacePath: z.string().optional(),
129130
})
130131
.strict();
131132

@@ -238,7 +239,7 @@ export const CreateConversationSchema = z
238239
type: z.literal('conversation:create'),
239240
uuid: UUIDSchema,
240241
title: z.string().optional(),
241-
workspace_id: z.string().optional(),
242+
workspace_path: z.string().optional(),
242243
})
243244
.strict();
244245

@@ -254,6 +255,7 @@ export const GetConversationsSchema = z
254255
.object({
255256
type: z.literal('conversation:list'),
256257
uuid: UUIDSchema,
258+
workspace_path: z.string().optional(),
257259
})
258260
.strict();
259261

@@ -1276,6 +1278,7 @@ export const ConversationCreatedSchema = z
12761278
uuid: UUIDSchema,
12771279
session_id: SessionIdSchema,
12781280
title: z.string(),
1281+
workspace_path: z.string().optional(),
12791282
})
12801283
.strict();
12811284

@@ -1298,6 +1301,7 @@ export const ConversationListSchema = z
12981301
title: z.string(),
12991302
updated_at: z.number(),
13001303
message_count: z.number(),
1304+
workspace_path: z.string().optional(),
13011305
})
13021306
.strict()
13031307
),

0 commit comments

Comments
 (0)