From 26d41d5a0b1856170daa524d754b9a3623408e0c Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:24:23 +0800 Subject: [PATCH 1/6] [feat] support the delete wortkspace --- src/apps/desktop/src/api/commands.rs | 85 +++++++++++++++++++ src/apps/desktop/src/lib.rs | 1 + .../sections/workspaces/WorkspaceItem.tsx | 47 ++++++++++ .../api/service-api/GlobalAPI.ts | 10 +++ .../contexts/WorkspaceContext.ts | 1 + .../contexts/WorkspaceProvider.tsx | 6 ++ .../services/business/workspaceManager.ts | 43 ++++++++++ src/web-ui/src/locales/en-US/common.json | 8 ++ src/web-ui/src/shared/types/global-state.ts | 1 + 9 files changed, 202 insertions(+) diff --git a/src/apps/desktop/src/api/commands.rs b/src/apps/desktop/src/api/commands.rs index 5fd9af33ad..0715ce823d 100644 --- a/src/apps/desktop/src/api/commands.rs +++ b/src/apps/desktop/src/api/commands.rs @@ -387,6 +387,12 @@ pub struct DeleteAssistantWorkspaceRequest { pub workspace_id: String, } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeleteWorkspaceRequest { + pub workspace_id: String, +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ResetAssistantWorkspaceRequest { @@ -1654,6 +1660,85 @@ pub async fn delete_assistant_workspace( Ok(()) } +/// Remove a workspace from the registry. +/// +/// Unlike `delete_assistant_workspace`, this does NOT delete the workspace's +/// project directory on disk — the user's files are preserved. It only: +/// 1. Closes the workspace if it is the active one. +/// 2. Unregisters a remote restore entry for remote workspaces. +/// 3. Removes the workspace from the in-memory registry and recent list. +/// 4. Reapplies (or clears) the active workspace context. +/// +/// This is the "delete" counterpart to "close": close unloads but keeps the +/// workspace in the list; delete removes it from the list entirely. +#[tauri::command] +pub async fn delete_workspace( + state: State<'_, AppState>, + app: tauri::AppHandle, + request: DeleteWorkspaceRequest, +) -> Result<(), String> { + let workspace_info = state + .workspace_service + .get_workspace(&request.workspace_id) + .await + .ok_or_else(|| format!("Workspace not found: {}", request.workspace_id))?; + + let is_active_workspace = state + .workspace_service + .get_current_workspace() + .await + .map(|workspace| workspace.id == request.workspace_id) + .unwrap_or(false); + + if is_active_workspace { + state + .workspace_service + .close_workspace(&request.workspace_id) + .await + .map_err(|e| format!("Failed to close workspace before deletion: {}", e))?; + } + + if workspace_info.workspace_kind == WorkspaceKind::Remote { + if let Some(rw) = remote_workspace_from_info(&workspace_info) { + state + .unregister_remote_workspace_entry(&rw.connection_id, &rw.remote_path) + .await; + } + } + + state + .workspace_service + .remove_workspace(&request.workspace_id) + .await + .map_err(|e| format!("Failed to remove workspace state: {}", e))?; + + if let Some(current_workspace) = state.workspace_service.get_current_workspace().await { + apply_active_workspace_context(&state, &app, ¤t_workspace, None).await; + } else { + clear_active_workspace_context(&state, &app, None).await; + } + + if let Err(e) = state + .workspace_identity_watch_service + .sync_watched_workspaces() + .await + { + warn!( + "Failed to sync workspace identity watchers after workspace deletion: {}", + e + ); + } + + info!( + "Workspace deleted: workspace_id={}, kind={}, path={}", + request.workspace_id, + workspace_info.workspace_kind, + workspace_info.root_path.display() + ); + + Ok(()) +} + async fn clear_directory_contents(directory: &Path) -> Result<(), String> { tokio::fs::create_dir_all(directory).await.map_err(|e| { format!( diff --git a/src/apps/desktop/src/lib.rs b/src/apps/desktop/src/lib.rs index ec760f6e25..66ba2a24fa 100644 --- a/src/apps/desktop/src/lib.rs +++ b/src/apps/desktop/src/lib.rs @@ -1103,6 +1103,7 @@ pub async fn _run() { delete_assistant_workspace, reset_assistant_workspace, close_workspace, + delete_workspace, set_active_workspace, reorder_opened_workspaces, get_current_workspace, diff --git a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx index fe7f7d3051..1a29333593 100644 --- a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx +++ b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx @@ -83,6 +83,7 @@ const WorkspaceItem: React.FC = ({ setActiveWorkspace, closeWorkspaceById, deleteAssistantWorkspace, + deleteWorkspace, resetAssistantWorkspace, } = useWorkspaceContext(); const { switchLeftPanelTab } = useApp(); @@ -109,10 +110,12 @@ const WorkspaceItem: React.FC = ({ const [worktreeModalOpen, setWorktreeModalOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteWorktreeDialogOpen, setDeleteWorktreeDialogOpen] = useState(false); + const [deleteWorkspaceDialogOpen, setDeleteWorkspaceDialogOpen] = useState(false); const [resetDialogOpen, setResetDialogOpen] = useState(false); const [relatedPathsDialogOpen, setRelatedPathsDialogOpen] = useState(false); const [isDeletingAssistant, setIsDeletingAssistant] = useState(false); const [isDeletingWorktree, setIsDeletingWorktree] = useState(false); + const [isDeletingWorkspace, setIsDeletingWorkspace] = useState(false); const [isResettingWorkspace, setIsResettingWorkspace] = useState(false); const [sessionsCollapsed, setSessionsCollapsed] = useState(false); const [searchIndexModalOpen, setSearchIndexModalOpen] = useState(false); @@ -475,6 +478,30 @@ const WorkspaceItem: React.FC = ({ } }, [closeWorkspaceById, t, workspace.id]); + const handleRequestDeleteWorkspace = useCallback(() => { + setMenuOpen(false); + setDeleteWorkspaceDialogOpen(true); + }, []); + + const handleConfirmDeleteWorkspace = useCallback(async () => { + if (isDeletingWorkspace) { + return; + } + + setIsDeletingWorkspace(true); + try { + await deleteWorkspace(workspace.id); + notificationService.success(t('nav.workspaces.workspaceDeleted'), { duration: 2500 }); + } catch (error) { + notificationService.error( + error instanceof Error ? error.message : t('nav.workspaces.deleteWorkspaceFailed'), + { duration: 4000 } + ); + } finally { + setIsDeletingWorkspace(false); + } + }, [deleteWorkspace, isDeletingWorkspace, t, workspace.id]); + const handleOpenSessionBatchModal = useCallback(() => { setMenuOpen(false); setSessionBatchModalOpen(true); @@ -1288,6 +1315,15 @@ const WorkspaceItem: React.FC = ({ {t('nav.workspaces.actions.close')} + , document.body )} @@ -1326,6 +1362,17 @@ const WorkspaceItem: React.FC = ({ confirmDanger preview={`${t('nav.workspaces.deleteWorktreeDialog.pathLabel')}\n${workspace.rootPath}`} /> + setDeleteWorkspaceDialogOpen(false)} + onConfirm={() => { void handleConfirmDeleteWorkspace(); }} + title={t('nav.workspaces.deleteWorkspaceDialog.title', { name: workspaceDisplayName })} + message={t('nav.workspaces.deleteWorkspaceDialog.message')} + confirmText={t('nav.workspaces.actions.deleteWorkspace')} + cancelText={t('actions.cancel')} + confirmDanger + preview={`${t('nav.workspaces.deleteWorkspaceDialog.pathLabel')}\n${workspace.rootPath}`} + /> {relatedPathsDialogOpen && ( { + try { + await api.invoke('delete_workspace', { + request: { workspaceId } + }); + } catch (error) { + throw createTauriCommandError('delete_workspace', error, { workspaceId }); + } + } + async resetAssistantWorkspace(workspaceId: string): Promise { try { return await api.invoke('reset_assistant_workspace', { diff --git a/src/web-ui/src/infrastructure/contexts/WorkspaceContext.ts b/src/web-ui/src/infrastructure/contexts/WorkspaceContext.ts index 79aed11d10..398d31670c 100644 --- a/src/web-ui/src/infrastructure/contexts/WorkspaceContext.ts +++ b/src/web-ui/src/infrastructure/contexts/WorkspaceContext.ts @@ -25,6 +25,7 @@ export interface WorkspaceContextValue extends WorkspaceState { closeWorkspace: () => Promise; closeWorkspaceById: (workspaceId: string) => Promise; deleteAssistantWorkspace: (workspaceId: string) => Promise; + deleteWorkspace: (workspaceId: string) => Promise; resetAssistantWorkspace: (workspaceId: string) => Promise; switchWorkspace: (workspace: WorkspaceInfo) => Promise; setActiveWorkspace: (workspaceId: string) => Promise; diff --git a/src/web-ui/src/infrastructure/contexts/WorkspaceProvider.tsx b/src/web-ui/src/infrastructure/contexts/WorkspaceProvider.tsx index a9d9639b59..dce2e364ad 100644 --- a/src/web-ui/src/infrastructure/contexts/WorkspaceProvider.tsx +++ b/src/web-ui/src/infrastructure/contexts/WorkspaceProvider.tsx @@ -231,6 +231,10 @@ export const WorkspaceProvider: React.FC = ({ children } return await workspaceManager.deleteAssistantWorkspace(workspaceId); }, []); + const deleteWorkspace = useCallback(async (workspaceId: string): Promise => { + return await workspaceManager.deleteWorkspace(workspaceId); + }, []); + const resetAssistantWorkspace = useCallback(async (workspaceId: string): Promise => { return await workspaceManager.resetAssistantWorkspace(workspaceId); }, []); @@ -295,6 +299,7 @@ export const WorkspaceProvider: React.FC = ({ children } closeWorkspace, closeWorkspaceById, deleteAssistantWorkspace, + deleteWorkspace, resetAssistantWorkspace, switchWorkspace, setActiveWorkspace, @@ -314,6 +319,7 @@ export const WorkspaceProvider: React.FC = ({ children } closeWorkspace, closeWorkspaceById, deleteAssistantWorkspace, + deleteWorkspace, resetAssistantWorkspace, switchWorkspace, setActiveWorkspace, diff --git a/src/web-ui/src/infrastructure/services/business/workspaceManager.ts b/src/web-ui/src/infrastructure/services/business/workspaceManager.ts index b01a5fbd77..29222a83e2 100644 --- a/src/web-ui/src/infrastructure/services/business/workspaceManager.ts +++ b/src/web-ui/src/infrastructure/services/business/workspaceManager.ts @@ -740,6 +740,49 @@ class WorkspaceManager { } } + public async deleteWorkspace(workspaceId: string): Promise { + try { + this.setLoading(true); + this.setError(null); + + log.info('Deleting workspace', { workspaceId }); + + const removedWorkspace = this.state.openedWorkspaces.get(workspaceId); + if (removedWorkspace) { + await this.cancelRunningSessionsForWorkspace(removedWorkspace); + } + + await globalStateAPI.deleteWorkspace(workspaceId); + + if (removedWorkspace) { + const { flowChatStore } = await import('@/flow_chat/store/FlowChatStore'); + flowChatStore.removeSessionsForWorkspace(removedWorkspace); + } + + const [currentWorkspace, recentWorkspaces, openedWorkspaces] = await Promise.all([ + globalStateAPI.getCurrentWorkspace(), + globalStateAPI.getRecentWorkspaces(), + globalStateAPI.getOpenedWorkspaces(), + ]); + + this.updateWorkspaceState( + currentWorkspace, + recentWorkspaces, + openedWorkspaces, + false, + null, + { type: 'workspace:removed', workspaceId } + ); + + this.emit({ type: 'workspace:active-changed', workspace: currentWorkspace }); + } catch (error) { + log.error('Failed to delete workspace', { workspaceId, error }); + const errorMessage = error instanceof Error ? error.message : String(error); + this.updateState({ loading: false, error: errorMessage }, { type: 'workspace:error', error: errorMessage }); + throw error; + } + } + public async resetAssistantWorkspace(workspaceId: string): Promise { try { this.setLoading(true); diff --git a/src/web-ui/src/locales/en-US/common.json b/src/web-ui/src/locales/en-US/common.json index ae5375c3ef..16bf517c54 100644 --- a/src/web-ui/src/locales/en-US/common.json +++ b/src/web-ui/src/locales/en-US/common.json @@ -336,6 +336,8 @@ "createAssistantFailed": "Failed to create personal assistant", "deleteAssistantFailed": "Failed to delete personal assistant", "assistantDeleted": "Personal assistant deleted", + "deleteWorkspaceFailed": "Failed to delete workspace", + "workspaceDeleted": "Workspace deleted", "resetWorkspaceFailed": "Failed to reset workspace", "workspaceReset": "Workspace reset", "reorderFailed": "Failed to reorder workspaces", @@ -364,6 +366,7 @@ "newSession": "New session", "newAssistant": "New personal assistant", "deleteAssistant": "Delete personal assistant", + "deleteWorkspace": "Delete workspace", "resetWorkspace": "Reset workspace", "initAgents": "Initialize AGENTS.md", "manageRelatedPaths": "Related directories", @@ -410,6 +413,11 @@ "title": "Delete {{name}}?", "message": "This will close the workspace and remove the worktree directory.", "pathLabel": "Worktree path" + }, + "deleteWorkspaceDialog": { + "title": "Delete {{name}}?", + "message": "This removes the workspace from the list. The project files on disk are not deleted and can be reopened later.", + "pathLabel": "Workspace path" } }, "agents": { diff --git a/src/web-ui/src/shared/types/global-state.ts b/src/web-ui/src/shared/types/global-state.ts index 4ac2b95169..c2342bfc7f 100644 --- a/src/web-ui/src/shared/types/global-state.ts +++ b/src/web-ui/src/shared/types/global-state.ts @@ -200,6 +200,7 @@ export interface GlobalStateAPI { ): Promise; createAssistantWorkspace(): Promise; deleteAssistantWorkspace(workspaceId: string): Promise; + deleteWorkspace(workspaceId: string): Promise; resetAssistantWorkspace(workspaceId: string): Promise; closeWorkspace(workspaceId: string): Promise; setActiveWorkspace(workspaceId: string): Promise; From 2b18c593dc6dd89ca912eaceebe387df582a614e Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:31:23 +0800 Subject: [PATCH 2/6] fix(web-ui): complete deleteWorkspace implementation to fix build - Add deleteWorkspace to WorkspaceProvider useState initializers (both success and fallback branches) so it matches WorkspaceContextValue. - Add deleteWorkspace implementation in createGlobalStateAPI delegating to globalAPI.deleteWorkspace. - Fix react-hooks/rules-of-hooks violation in ReadFileDisplay by moving useState before the conditional early return. --- src/web-ui/src/flow_chat/tool-cards/ReadFileDisplay.tsx | 4 ++-- src/web-ui/src/infrastructure/contexts/WorkspaceProvider.tsx | 4 ++++ src/web-ui/src/shared/types/global-state.ts | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/web-ui/src/flow_chat/tool-cards/ReadFileDisplay.tsx b/src/web-ui/src/flow_chat/tool-cards/ReadFileDisplay.tsx index 2f0740a8c9..2af7f57981 100644 --- a/src/web-ui/src/flow_chat/tool-cards/ReadFileDisplay.tsx +++ b/src/web-ui/src/flow_chat/tool-cards/ReadFileDisplay.tsx @@ -112,6 +112,8 @@ export const ReadFileDisplay: React.FC = React.memo(({ status !== 'error' ); + const [shouldExpand, setShouldExpand] = useState(true); + if (status === 'error') { return null; } @@ -154,8 +156,6 @@ export const ReadFileDisplay: React.FC = React.memo(({ return null; }; - const [shouldExpand, setShouldExpand] = useState(true); - const handleMouseDown = () => { setShouldExpand(true); } diff --git a/src/web-ui/src/infrastructure/contexts/WorkspaceProvider.tsx b/src/web-ui/src/infrastructure/contexts/WorkspaceProvider.tsx index dce2e364ad..6bbfdedfb6 100644 --- a/src/web-ui/src/infrastructure/contexts/WorkspaceProvider.tsx +++ b/src/web-ui/src/infrastructure/contexts/WorkspaceProvider.tsx @@ -39,6 +39,8 @@ export const WorkspaceProvider: React.FC = ({ children } closeWorkspaceById: async (workspaceId: string) => workspaceManager.closeWorkspaceById(workspaceId), deleteAssistantWorkspace: async (workspaceId: string) => workspaceManager.deleteAssistantWorkspace(workspaceId), + deleteWorkspace: async (workspaceId: string) => + workspaceManager.deleteWorkspace(workspaceId), resetAssistantWorkspace: async (workspaceId: string) => workspaceManager.resetAssistantWorkspace(workspaceId), switchWorkspace: async (workspace: WorkspaceInfo) => workspaceManager.switchWorkspace(workspace), @@ -87,6 +89,8 @@ export const WorkspaceProvider: React.FC = ({ children } closeWorkspaceById: async (workspaceId: string) => workspaceManager.closeWorkspaceById(workspaceId), deleteAssistantWorkspace: async (workspaceId: string) => workspaceManager.deleteAssistantWorkspace(workspaceId), + deleteWorkspace: async (workspaceId: string) => + workspaceManager.deleteWorkspace(workspaceId), resetAssistantWorkspace: async (workspaceId: string) => workspaceManager.resetAssistantWorkspace(workspaceId), switchWorkspace: async (workspace: WorkspaceInfo) => workspaceManager.switchWorkspace(workspace), diff --git a/src/web-ui/src/shared/types/global-state.ts b/src/web-ui/src/shared/types/global-state.ts index c2342bfc7f..3c2b2d3aa1 100644 --- a/src/web-ui/src/shared/types/global-state.ts +++ b/src/web-ui/src/shared/types/global-state.ts @@ -410,6 +410,10 @@ export function createGlobalStateAPI(): GlobalStateAPI { return await globalAPI.deleteAssistantWorkspace(workspaceId); }, + async deleteWorkspace(workspaceId: string): Promise { + return await globalAPI.deleteWorkspace(workspaceId); + }, + async resetAssistantWorkspace(workspaceId: string): Promise { return mapWorkspaceInfo(await globalAPI.resetAssistantWorkspace(workspaceId)); }, From 752c821cc5f56e8be165c920496b0a2454455be0 Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:19:56 +0800 Subject: [PATCH 3/6] feat(desktop,web-ui): delete workspace removes folder from disk and add zh-CN/zh-TW i18n - delete_workspace Rust command now recursively deletes the workspace root_path for local workspaces; remote workspaces skip disk deletion. - Add is_unsafe_workspace_deletion_path guard to refuse filesystem roots and relative paths. - Close the confirm dialog after a successful deletion so the UI reflects the action immediately. - Add missing zh-CN/zh-TW translations for deleteWorkspace, deleteWorkspaceFailed, workspaceDeleted, and deleteWorkspaceDialog, and update the dialog message across all locales to reflect the destructive behavior. --- src/apps/desktop/src/api/commands.rs | 57 ++++++++++++++++--- .../sections/workspaces/WorkspaceItem.tsx | 1 + src/web-ui/src/app/layout/AppLayout.tsx | 2 +- src/web-ui/src/locales/en-US/common.json | 2 +- src/web-ui/src/locales/zh-CN/common.json | 8 +++ src/web-ui/src/locales/zh-TW/common.json | 8 +++ 6 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/apps/desktop/src/api/commands.rs b/src/apps/desktop/src/api/commands.rs index 0715ce823d..15bd6ce7d2 100644 --- a/src/apps/desktop/src/api/commands.rs +++ b/src/apps/desktop/src/api/commands.rs @@ -1660,17 +1660,19 @@ pub async fn delete_assistant_workspace( Ok(()) } -/// Remove a workspace from the registry. +/// Remove a workspace from the registry and delete its project directory on disk. /// -/// Unlike `delete_assistant_workspace`, this does NOT delete the workspace's -/// project directory on disk — the user's files are preserved. It only: +/// This is the destructive counterpart to "close": close unloads but keeps the +/// workspace in the list and leaves files on disk; delete removes the workspace +/// from the list and recursively deletes its `root_path` directory. It: /// 1. Closes the workspace if it is the active one. /// 2. Unregisters a remote restore entry for remote workspaces. -/// 3. Removes the workspace from the in-memory registry and recent list. -/// 4. Reapplies (or clears) the active workspace context. +/// 3. Deletes the workspace directory on disk for local workspaces. +/// 4. Removes the workspace from the in-memory registry and recent list. +/// 5. Reapplies (or clears) the active workspace context. /// -/// This is the "delete" counterpart to "close": close unloads but keeps the -/// workspace in the list; delete removes it from the list entirely. +/// Remote workspaces cannot be deleted locally; their `root_path` lives on the +/// remote host, so disk deletion is skipped for them. #[tauri::command] pub async fn delete_workspace( state: State<'_, AppState>, @@ -1706,6 +1708,25 @@ pub async fn delete_workspace( } } + let workspace_path_str = workspace_info.root_path.to_string_lossy().to_string(); + let is_remote = is_remote_path(&workspace_path_str).await + || workspace_info.workspace_kind == WorkspaceKind::Remote; + + if !is_remote && !workspace_path_str.trim().is_empty() { + if is_unsafe_workspace_deletion_path(&workspace_info.root_path) { + return Err(format!( + "Refusing to delete unsafe workspace path: {}", + workspace_info.root_path.display() + )); + } + + state + .filesystem_service + .delete_directory(&workspace_path_str, true) + .await + .map_err(|e| format!("Failed to delete workspace files: {}", e))?; + } + state .workspace_service .remove_workspace(&request.workspace_id) @@ -1739,6 +1760,28 @@ pub async fn delete_workspace( Ok(()) } +/// Returns true if the path is unsafe to delete as a workspace directory. +/// +/// Blocks filesystem roots, relative paths, and any path with fewer than two +/// components (e.g. `/`, `C:\`) to avoid catastrophic deletions. +fn is_unsafe_workspace_deletion_path(path: &Path) -> bool { + if path.is_relative() { + return true; + } + + let mut components = path.components(); + let first = components.next(); + if first.is_none() { + return true; + } + + if components.next().is_none() { + return true; + } + + false +} + async fn clear_directory_contents(directory: &Path) -> Result<(), String> { tokio::fs::create_dir_all(directory).await.map_err(|e| { format!( diff --git a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx index 1a29333593..40a546823a 100644 --- a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx +++ b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx @@ -491,6 +491,7 @@ const WorkspaceItem: React.FC = ({ setIsDeletingWorkspace(true); try { await deleteWorkspace(workspace.id); + setDeleteWorkspaceDialogOpen(false); notificationService.success(t('nav.workspaces.workspaceDeleted'), { duration: 2500 }); } catch (error) { notificationService.error( diff --git a/src/web-ui/src/app/layout/AppLayout.tsx b/src/web-ui/src/app/layout/AppLayout.tsx index 2be43b22e7..ea5dc27395 100644 --- a/src/web-ui/src/app/layout/AppLayout.tsx +++ b/src/web-ui/src/app/layout/AppLayout.tsx @@ -735,7 +735,7 @@ const AppLayout: React.FC = ({ className = '' }) => { isOpen={showNewProjectDialog} onClose={() => setShowNewProjectDialog(false)} onConfirm={handleConfirmNewProject} - defaultParentPath={hasWorkspace ? currentWorkspace?.rootPath : undefined} + defaultParentPath={hasWorkspace ? currentWorkspace?.rootPath : '/storage/Users/currentUser/Documents'} /> )} diff --git a/src/web-ui/src/locales/en-US/common.json b/src/web-ui/src/locales/en-US/common.json index 16bf517c54..2f09f62046 100644 --- a/src/web-ui/src/locales/en-US/common.json +++ b/src/web-ui/src/locales/en-US/common.json @@ -416,7 +416,7 @@ }, "deleteWorkspaceDialog": { "title": "Delete {{name}}?", - "message": "This removes the workspace from the list. The project files on disk are not deleted and can be reopened later.", + "message": "This will permanently delete the workspace directory and all files inside it. This action cannot be undone.", "pathLabel": "Workspace path" } }, diff --git a/src/web-ui/src/locales/zh-CN/common.json b/src/web-ui/src/locales/zh-CN/common.json index 8100d67ce3..a5ec8c74a9 100644 --- a/src/web-ui/src/locales/zh-CN/common.json +++ b/src/web-ui/src/locales/zh-CN/common.json @@ -336,6 +336,8 @@ "createAssistantFailed": "新建个人助理失败", "deleteAssistantFailed": "删除个人助理失败", "assistantDeleted": "已删除个人助理", + "deleteWorkspaceFailed": "删除工作区失败", + "workspaceDeleted": "已删除工作区", "resetWorkspaceFailed": "重置工作区失败", "workspaceReset": "已重置工作区", "reorderFailed": "调整工作区顺序失败", @@ -364,6 +366,7 @@ "newSession": "新建会话", "newAssistant": "新建个人助理", "deleteAssistant": "删除个人助理", + "deleteWorkspace": "删除工作区", "resetWorkspace": "重置工作区", "initAgents": "初始化 AGENTS.md", "manageRelatedPaths": "关联目录", @@ -410,6 +413,11 @@ "title": "删除 {{name}}?", "message": "此操作会关闭该工作区,并删除对应的 worktree 目录。", "pathLabel": "Worktree 路径" + }, + "deleteWorkspaceDialog": { + "title": "删除 {{name}}?", + "message": "此操作会永久删除该工作区目录及其中的所有文件,无法恢复。", + "pathLabel": "工作区路径" } }, "agents": { diff --git a/src/web-ui/src/locales/zh-TW/common.json b/src/web-ui/src/locales/zh-TW/common.json index 4f02cc2e68..81f4c9b480 100644 --- a/src/web-ui/src/locales/zh-TW/common.json +++ b/src/web-ui/src/locales/zh-TW/common.json @@ -336,6 +336,8 @@ "createAssistantFailed": "新增個人助理失敗", "deleteAssistantFailed": "刪除個人助理失敗", "assistantDeleted": "已刪除個人助理", + "deleteWorkspaceFailed": "刪除工作區失敗", + "workspaceDeleted": "已刪除工作區", "resetWorkspaceFailed": "重設工作區失敗", "workspaceReset": "已重設工作區", "reorderFailed": "調整工作區順序失敗", @@ -364,6 +366,7 @@ "newSession": "新增會話", "newAssistant": "新增個人助理", "deleteAssistant": "刪除個人助理", + "deleteWorkspace": "刪除工作區", "resetWorkspace": "重設工作區", "initAgents": "初始化 AGENTS.md", "manageRelatedPaths": "關聯目錄", @@ -410,6 +413,11 @@ "title": "刪除 {{name}}?", "message": "此操作會關閉該工作區,並刪除對應的 worktree 目錄。", "pathLabel": "Worktree 路徑" + }, + "deleteWorkspaceDialog": { + "title": "刪除 {{name}}?", + "message": "此操作會永久刪除該工作區目錄及其中的所有檔案,無法復原。", + "pathLabel": "工作區路徑" } }, "agents": { From 9773ee73face6b50905bd67aa1567d097d093579 Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:00:58 +0800 Subject: [PATCH 4/6] style(web-ui): drop Workspace Path label and preview background in delete dialog Move the workspace rootPath into the message body as monospace text instead of using the preview block, removing the 'Workspace Path' label, background color, and border while keeping the path at a smaller font size (font-size-xs) than the message body. --- .../sections/workspaces/WorkspaceItem.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx index 40a546823a..555497f548 100644 --- a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx +++ b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx @@ -1368,11 +1368,24 @@ const WorkspaceItem: React.FC = ({ onClose={() => setDeleteWorkspaceDialogOpen(false)} onConfirm={() => { void handleConfirmDeleteWorkspace(); }} title={t('nav.workspaces.deleteWorkspaceDialog.title', { name: workspaceDisplayName })} - message={t('nav.workspaces.deleteWorkspaceDialog.message')} + message={ + <> +
{t('nav.workspaces.deleteWorkspaceDialog.message')}
+
+ {workspace.rootPath} +
+ + } confirmText={t('nav.workspaces.actions.deleteWorkspace')} cancelText={t('actions.cancel')} confirmDanger - preview={`${t('nav.workspaces.deleteWorkspaceDialog.pathLabel')}\n${workspace.rootPath}`} /> {relatedPathsDialogOpen && ( From 92c91fefa5a8fcd52557a296e61dcc6ec24c0689 Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Tue, 23 Jun 2026 09:48:02 +0800 Subject: [PATCH 5/6] fix(web-ui): default new project parent path to Documents for assistant workspace When the active workspace is an assistant workspace (auto-activated on startup) or no workspace is open, default the New Project dialog parent path to /storage/Users/currentUser/Documents instead of the assistant workspace directory. --- src/web-ui/src/app/layout/AppLayout.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/web-ui/src/app/layout/AppLayout.tsx b/src/web-ui/src/app/layout/AppLayout.tsx index ea5dc27395..6f56d94c25 100644 --- a/src/web-ui/src/app/layout/AppLayout.tsx +++ b/src/web-ui/src/app/layout/AppLayout.tsx @@ -735,7 +735,11 @@ const AppLayout: React.FC = ({ className = '' }) => { isOpen={showNewProjectDialog} onClose={() => setShowNewProjectDialog(false)} onConfirm={handleConfirmNewProject} - defaultParentPath={hasWorkspace ? currentWorkspace?.rootPath : '/storage/Users/currentUser/Documents'} + defaultParentPath={ + hasWorkspace && currentWorkspace?.workspaceKind !== WorkspaceKind.Assistant + ? currentWorkspace?.rootPath + : '/storage/Users/currentUser/Documents' + } /> )} From f62c961daabff93e97b5ac1701897719043d4919 Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Tue, 23 Jun 2026 09:52:35 +0800 Subject: [PATCH 6/6] style(web-ui): add background to delete workspace dialog path display Wrap the workspace rootPath in a subtle bg-subtle block with padding and rounded corners for better visual separation from the message text. --- .../components/NavPanel/sections/workspaces/WorkspaceItem.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx index 555497f548..2b55a0e7b6 100644 --- a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx +++ b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx @@ -1373,9 +1373,12 @@ const WorkspaceItem: React.FC = ({
{t('nav.workspaces.deleteWorkspaceDialog.message')}