From 3e882813788ad89d374a4106e89235df1ec1ff97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=99=A8?= Date: Wed, 22 Apr 2026 15:36:30 +0800 Subject: [PATCH 1/7] feat(explorer): add openOrFocusTab action in store + tests Dedup-by-path tab open: if a tab with the same rootPath already exists, activate it instead of creating a duplicate. Enables syncing the File Explorer with session workingDirectory changes without churning tabs. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/renderer/stores/explorerStore.ts | 18 ++++++++ tests/unit/renderer/explorerStore.test.ts | 56 +++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/unit/renderer/explorerStore.test.ts diff --git a/src/renderer/stores/explorerStore.ts b/src/renderer/stores/explorerStore.ts index 5e45fe37..08d68460 100644 --- a/src/renderer/stores/explorerStore.ts +++ b/src/renderer/stores/explorerStore.ts @@ -29,6 +29,7 @@ interface ExplorerState { // Actions addTab: (rootPath: string, label: string) => void; + openOrFocusTab: (rootPath: string, label: string) => void; closeTab: (id: string) => void; setActiveTab: (id: string) => void; setDirContents: (path: string, contents: FileInfo[]) => void; @@ -61,6 +62,23 @@ export const useExplorerStore = create((set) => ({ })); }, + openOrFocusTab: (rootPath, label) => { + set((state) => { + const existing = state.tabs.find((t) => t.rootPath === rootPath); + if (existing) { + return state.activeTabId === existing.id + ? state + : { ...state, activeTabId: existing.id }; + } + const id = `tab-${++tabCounter}`; + return { + ...state, + tabs: [...state.tabs, { id, rootPath, label }], + activeTabId: id, + }; + }); + }, + closeTab: (id) => { set((state) => { const newTabs = state.tabs.filter((t) => t.id !== id); diff --git a/tests/unit/renderer/explorerStore.test.ts b/tests/unit/renderer/explorerStore.test.ts new file mode 100644 index 00000000..c34adaf8 --- /dev/null +++ b/tests/unit/renderer/explorerStore.test.ts @@ -0,0 +1,56 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { useExplorerStore } from '../../../src/renderer/stores/explorerStore'; + +describe('explorerStore.openOrFocusTab', () => { + beforeEach(() => { + useExplorerStore.getState().reset(); + }); + + it('creates a new tab when rootPath is not open', () => { + const { openOrFocusTab } = useExplorerStore.getState(); + openOrFocusTab('/tmp/a', 'a'); + + const { tabs, activeTabId } = useExplorerStore.getState(); + expect(tabs).toHaveLength(1); + expect(tabs[0].rootPath).toBe('/tmp/a'); + expect(tabs[0].label).toBe('a'); + expect(activeTabId).toBe(tabs[0].id); + }); + + it('activates existing tab without duplicating when rootPath matches', () => { + const { openOrFocusTab } = useExplorerStore.getState(); + openOrFocusTab('/tmp/a', 'a'); + openOrFocusTab('/tmp/b', 'b'); + const tabAId = useExplorerStore.getState().tabs[0].id; + + // Switch back to /tmp/a — should focus existing, not add a third tab + openOrFocusTab('/tmp/a', 'a'); + + const { tabs, activeTabId } = useExplorerStore.getState(); + expect(tabs).toHaveLength(2); + expect(activeTabId).toBe(tabAId); + }); + + it('is a no-op when rootPath already matches the active tab', () => { + const { openOrFocusTab } = useExplorerStore.getState(); + openOrFocusTab('/tmp/a', 'a'); + const stateBefore = useExplorerStore.getState(); + + openOrFocusTab('/tmp/a', 'a'); + + const stateAfter = useExplorerStore.getState(); + expect(stateAfter.tabs).toBe(stateBefore.tabs); + expect(stateAfter.activeTabId).toBe(stateBefore.activeTabId); + }); + + it('keeps previously opened tabs when adding a new one', () => { + const { openOrFocusTab } = useExplorerStore.getState(); + openOrFocusTab('/tmp/a', 'a'); + openOrFocusTab('/tmp/b', 'b'); + openOrFocusTab('/tmp/c', 'c'); + + const { tabs, activeTabId } = useExplorerStore.getState(); + expect(tabs.map((t) => t.rootPath)).toEqual(['/tmp/a', '/tmp/b', '/tmp/c']); + expect(activeTabId).toBe(tabs[2].id); + }); +}); From 152b90d1ea474bdb86f47b6176b09b439fc0fd2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=99=A8?= Date: Wed, 22 Apr 2026 15:37:59 +0800 Subject: [PATCH 2/7] feat(explorer): sync file explorer with session working directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the one-shot initRef mount effect with a continuous subscription to appStore.workingDirectory. Whenever the active session's working directory changes, open a tab for it or activate an existing one with the same path. Other manually opened tabs are preserved. Switch TabBar's Plus button and the empty-state "打开项目目录" fallback to openOrFocusTab too, so manual opens also dedup by path. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../features/explorer/FileExplorerPanel.tsx | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/renderer/components/features/explorer/FileExplorerPanel.tsx b/src/renderer/components/features/explorer/FileExplorerPanel.tsx index 9e0b3443..e4bb9384 100644 --- a/src/renderer/components/features/explorer/FileExplorerPanel.tsx +++ b/src/renderer/components/features/explorer/FileExplorerPanel.tsx @@ -367,7 +367,7 @@ const FileTreeNode: React.FC<{ // ── TabBar ── const TabBar: React.FC = () => { - const { tabs, activeTabId, setActiveTab, closeTab, addTab } = useExplorerStore(); + const { tabs, activeTabId, setActiveTab, closeTab, openOrFocusTab } = useExplorerStore(); const workingDirectory = useAppStore((s) => s.workingDirectory); const handleAddTab = useCallback(async () => { @@ -379,16 +379,16 @@ const TabBar: React.FC = () => { if (response?.success && response.data) { const dirPath = response.data; const label = dirPath.split('/').pop() || dirPath; - addTab(dirPath, label); + openOrFocusTab(dirPath, label); } } catch { // Fallback: use working directory if (workingDirectory) { const label = workingDirectory.split('/').pop() || 'Root'; - addTab(workingDirectory, label); + openOrFocusTab(workingDirectory, label); } } - }, [addTab, workingDirectory]); + }, [openOrFocusTab, workingDirectory]); return (
@@ -432,18 +432,16 @@ interface FileExplorerPanelProps { export const FileExplorerPanel: React.FC = ({ onClose }) => { const { tabs, activeTabId, dirContents, pendingCreate, - addTab, setDirContents, setLoading, startCreate, cancelCreate, + openOrFocusTab, setDirContents, setLoading, startCreate, cancelCreate, } = useExplorerStore(); const workingDirectory = useAppStore((s) => s.workingDirectory); - const initRef = useRef(false); - // Auto-add working directory tab on first mount + // Sync active tab with session workingDirectory: open it or focus if already open. useEffect(() => { - if (initRef.current || tabs.length > 0 || !workingDirectory) return; - initRef.current = true; + if (!workingDirectory) return; const label = workingDirectory.split('/').pop() || 'Project'; - addTab(workingDirectory, label); - }, [workingDirectory, tabs.length, addTab]); + openOrFocusTab(workingDirectory, label); + }, [workingDirectory, openOrFocusTab]); // Load root directory contents when active tab changes const activeTab = tabs.find((t) => t.id === activeTabId); @@ -532,7 +530,7 @@ export const FileExplorerPanel: React.FC = ({ onClose }) onClick={() => { if (workingDirectory) { const label = workingDirectory.split('/').pop() || 'Project'; - addTab(workingDirectory, label); + openOrFocusTab(workingDirectory, label); } }} className="text-xs text-primary-400 hover:text-primary-300" From 1418c0a54e2792e8fc7c1353cf62d628f3e2c1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=99=A8?= Date: Wed, 22 Apr 2026 20:17:23 +0800 Subject: [PATCH 3/7] fix(ipc): route workspace:selectDirectory through domain API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The legacy channel 'workspace:select-directory' is declared in IPC_CHANNELS but has no registered handler in main — only the domain dispatcher workspace.ipc.ts 'case selectDirectory' is wired. Three callsites were still invoking the dead legacy channel and the errors were swallowed by try/catch, so users saw "clicking does nothing": - TitleBar workspace chip - TaskPanel WorkingFolder picker - Lab RealModePanel project directory picker All three now go through ipcService.invokeDomain(IPC_DOMAINS.WORKSPACE, 'selectDirectory'), matching FileExplorerPanel's TabBar path. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/renderer/components/TaskPanel/WorkingFolder.tsx | 4 ++-- src/renderer/components/TitleBar.tsx | 4 ++-- src/renderer/components/features/lab/gpt1/RealModePanel.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/renderer/components/TaskPanel/WorkingFolder.tsx b/src/renderer/components/TaskPanel/WorkingFolder.tsx index 415ade38..994792eb 100644 --- a/src/renderer/components/TaskPanel/WorkingFolder.tsx +++ b/src/renderer/components/TaskPanel/WorkingFolder.tsx @@ -8,7 +8,7 @@ import { useAppStore } from '../../stores/appStore'; import { useComposerStore } from '../../stores/composerStore'; import { useSessionStore } from '../../stores/sessionStore'; import { useI18n } from '../../hooks/useI18n'; -import { IPC_CHANNELS } from '@shared/ipc'; +import { IPC_CHANNELS, IPC_DOMAINS } from '@shared/ipc'; import { isWebMode } from '../../utils/platform'; import ipcService from '../../services/ipcService'; @@ -92,7 +92,7 @@ export const WorkingFolder: React.FC = () => { } return; } - const result = await ipcService.invoke(IPC_CHANNELS.WORKSPACE_SELECT_DIRECTORY); + const result = await ipcService.invokeDomain(IPC_DOMAINS.WORKSPACE, 'selectDirectory'); if (result) { setWorkingDirectory(result); } diff --git a/src/renderer/components/TitleBar.tsx b/src/renderer/components/TitleBar.tsx index fbf638b4..e493813a 100644 --- a/src/renderer/components/TitleBar.tsx +++ b/src/renderer/components/TitleBar.tsx @@ -7,7 +7,7 @@ import { useComposerStore } from '../stores/composerStore'; import { useDisclosure } from '../hooks/useDisclosure'; import { PanelLeftClose, PanelLeft, PanelRightClose, PanelRight, FolderOpen, FolderTree, GitBranch, FlaskConical, Monitor, Clock3, Sparkles } from 'lucide-react'; import { isWebMode } from '../utils/platform'; -import { IPC_CHANNELS } from '@shared/ipc'; +import { IPC_DOMAINS } from '@shared/ipc'; import ipcService from '../services/ipcService'; import { IconButton } from './primitives'; // 奶酪图标组件 @@ -64,7 +64,7 @@ export const TitleBar: React.FC = () => { if (isWebMode()) { selectedPath = window.prompt('输入工作目录路径', effectiveWorkingDirectory || '')?.trim() || null; } else { - selectedPath = await ipcService.invoke(IPC_CHANNELS.WORKSPACE_SELECT_DIRECTORY); + selectedPath = await ipcService.invokeDomain(IPC_DOMAINS.WORKSPACE, 'selectDirectory'); } if (selectedPath) { setComposerWorkingDirectory(selectedPath); diff --git a/src/renderer/components/features/lab/gpt1/RealModePanel.tsx b/src/renderer/components/features/lab/gpt1/RealModePanel.tsx index 2dd362a8..144fa503 100644 --- a/src/renderer/components/features/lab/gpt1/RealModePanel.tsx +++ b/src/renderer/components/features/lab/gpt1/RealModePanel.tsx @@ -22,7 +22,7 @@ import { CheckCircle2, XCircle, } from 'lucide-react'; -import { IPC_CHANNELS } from '../../../../../shared/ipc'; +import { IPC_CHANNELS, IPC_DOMAINS } from '../../../../../shared/ipc'; import { isWebMode } from '../../../../utils/platform'; import ipcService from '../../../../services/ipcService'; import type { @@ -198,7 +198,7 @@ export const RealModePanel: React.FC = () => { addLog('info', `已选择项目目录: ${manualPath.trim()}`); return; } - const selectedPath = await ipcService.invoke(IPC_CHANNELS.WORKSPACE_SELECT_DIRECTORY); + const selectedPath = await ipcService.invokeDomain(IPC_DOMAINS.WORKSPACE, 'selectDirectory'); if (selectedPath) { setProjectPath(selectedPath); setProjectUIStatus('downloaded'); From 580195dd8545c3884bd0d801368c64c888cddfa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=99=A8?= Date: Wed, 22 Apr 2026 20:29:41 +0800 Subject: [PATCH 4/7] feat(tauri): install plugin-dialog for native file pickers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registers tauri-plugin-dialog in the Rust builder, grants dialog:allow-open capability, and installs @tauri-apps/plugin-dialog on the JS side. The previous Electron→Tauri port left dialog.showOpenDialog as a no-op stub in miscCompat.ts, so every "select directory" surface was silently cancelled. The plugin provides the native directory picker the renderer will invoke directly. Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 10 ++++++++++ package.json | 1 + src-tauri/Cargo.toml | 1 + src-tauri/capabilities/default.json | 3 ++- src-tauri/src/main.rs | 1 + 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 9afd0c02..4a1cdbef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@modelcontextprotocol/sdk": "^1.27.1", "@supabase/supabase-js": "^2.90.1", "@tauri-apps/api": "^2.10.1", + "@tauri-apps/plugin-dialog": "^2.7.0", "@types/dagre": "^0.7.53", "@types/papaparse": "^5.5.2", "@ui-tars/sdk": "^1.2.3", @@ -2127,6 +2128,15 @@ "url": "https://opencollective.com/tauri" } }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.0.tgz", + "integrity": "sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmmirror.com/@tokenizer/token/-/token-0.3.0.tgz", diff --git a/package.json b/package.json index 2120688b..5b7811c8 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "@modelcontextprotocol/sdk": "^1.27.1", "@supabase/supabase-js": "^2.90.1", "@tauri-apps/api": "^2.10.1", + "@tauri-apps/plugin-dialog": "^2.7.0", "@types/dagre": "^0.7.53", "@types/papaparse": "^5.5.2", "@ui-tars/sdk": "^1.2.3", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a82af713..73ebb260 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -17,6 +17,7 @@ reqwest = { version = "0.12", default-features = false, features = ["blocking", ctrlc = "3.4" tauri-plugin-global-shortcut = "2" tauri-plugin-single-instance = "2" +tauri-plugin-dialog = "2" [profile.release] panic = "abort" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 1314cadc..6ddc4873 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -7,6 +7,7 @@ "core:default", "updater:default", "global-shortcut:allow-register", - "global-shortcut:allow-unregister" + "global-shortcut:allow-unregister", + "dialog:allow-open" ] } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 1cb8a177..6b368a36 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -376,6 +376,7 @@ fn main() { })) .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_global_shortcut::Builder::new().build()) + .plugin(tauri_plugin_dialog::init()) .manage(AppState::default()) .manage(NativeDesktopState::default()) .invoke_handler(tauri::generate_handler![ From 6a9ec0ef2f4cd81ad28caa1576a134f328c6632b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=99=A8?= Date: Wed, 22 Apr 2026 20:29:55 +0800 Subject: [PATCH 5/7] fix(ipc): open directory picker via plugin-dialog in renderer The main-process handleSelectDirectory calls dialog.showOpenDialog, but under Tauri that symbol resolves to the miscCompat stub that always returns canceled. Short-circuit in the renderer instead: when running in a Tauri webview, lazy-import @tauri-apps/plugin-dialog and invoke its native directory picker directly, then plumb the path into the existing stores. Web mode keeps the window.prompt fallback. Fixes the three dead callsites: TitleBar workspace chip, TaskPanel WorkingFolder picker, Lab RealModePanel project directory picker. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/TaskPanel/WorkingFolder.tsx | 13 ++++++++----- src/renderer/components/TitleBar.tsx | 10 +++++----- .../features/lab/gpt1/RealModePanel.tsx | 17 ++++++++++------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/renderer/components/TaskPanel/WorkingFolder.tsx b/src/renderer/components/TaskPanel/WorkingFolder.tsx index 994792eb..0ae64d0c 100644 --- a/src/renderer/components/TaskPanel/WorkingFolder.tsx +++ b/src/renderer/components/TaskPanel/WorkingFolder.tsx @@ -8,8 +8,8 @@ import { useAppStore } from '../../stores/appStore'; import { useComposerStore } from '../../stores/composerStore'; import { useSessionStore } from '../../stores/sessionStore'; import { useI18n } from '../../hooks/useI18n'; -import { IPC_CHANNELS, IPC_DOMAINS } from '@shared/ipc'; -import { isWebMode } from '../../utils/platform'; +import { IPC_CHANNELS } from '@shared/ipc'; +import { isWebMode, isTauriMode } from '../../utils/platform'; import ipcService from '../../services/ipcService'; interface FileInfo { @@ -92,9 +92,12 @@ export const WorkingFolder: React.FC = () => { } return; } - const result = await ipcService.invokeDomain(IPC_DOMAINS.WORKSPACE, 'selectDirectory'); - if (result) { - setWorkingDirectory(result); + if (isTauriMode()) { + const { open } = await import('@tauri-apps/plugin-dialog'); + const result = await open({ directory: true, multiple: false, title: '选择工作目录' }); + if (typeof result === 'string') { + setWorkingDirectory(result); + } } } catch (error) { console.error('Failed to select directory:', error); diff --git a/src/renderer/components/TitleBar.tsx b/src/renderer/components/TitleBar.tsx index e493813a..8b2bac30 100644 --- a/src/renderer/components/TitleBar.tsx +++ b/src/renderer/components/TitleBar.tsx @@ -6,9 +6,7 @@ import { useAppStore } from '../stores/appStore'; import { useComposerStore } from '../stores/composerStore'; import { useDisclosure } from '../hooks/useDisclosure'; import { PanelLeftClose, PanelLeft, PanelRightClose, PanelRight, FolderOpen, FolderTree, GitBranch, FlaskConical, Monitor, Clock3, Sparkles } from 'lucide-react'; -import { isWebMode } from '../utils/platform'; -import { IPC_DOMAINS } from '@shared/ipc'; -import ipcService from '../services/ipcService'; +import { isWebMode, isTauriMode } from '../utils/platform'; import { IconButton } from './primitives'; // 奶酪图标组件 const CheeseIcon: React.FC<{ className?: string }> = ({ className }) => ( @@ -63,8 +61,10 @@ export const TitleBar: React.FC = () => { let selectedPath: string | null = null; if (isWebMode()) { selectedPath = window.prompt('输入工作目录路径', effectiveWorkingDirectory || '')?.trim() || null; - } else { - selectedPath = await ipcService.invokeDomain(IPC_DOMAINS.WORKSPACE, 'selectDirectory'); + } else if (isTauriMode()) { + const { open } = await import('@tauri-apps/plugin-dialog'); + const result = await open({ directory: true, multiple: false, title: '选择工作目录' }); + selectedPath = typeof result === 'string' ? result : null; } if (selectedPath) { setComposerWorkingDirectory(selectedPath); diff --git a/src/renderer/components/features/lab/gpt1/RealModePanel.tsx b/src/renderer/components/features/lab/gpt1/RealModePanel.tsx index 144fa503..ac102dd1 100644 --- a/src/renderer/components/features/lab/gpt1/RealModePanel.tsx +++ b/src/renderer/components/features/lab/gpt1/RealModePanel.tsx @@ -22,8 +22,8 @@ import { CheckCircle2, XCircle, } from 'lucide-react'; -import { IPC_CHANNELS, IPC_DOMAINS } from '../../../../../shared/ipc'; -import { isWebMode } from '../../../../utils/platform'; +import { IPC_CHANNELS } from '../../../../../shared/ipc'; +import { isWebMode, isTauriMode } from '../../../../utils/platform'; import ipcService from '../../../../services/ipcService'; import type { PythonEnvStatus, @@ -198,11 +198,14 @@ export const RealModePanel: React.FC = () => { addLog('info', `已选择项目目录: ${manualPath.trim()}`); return; } - const selectedPath = await ipcService.invokeDomain(IPC_DOMAINS.WORKSPACE, 'selectDirectory'); - if (selectedPath) { - setProjectPath(selectedPath); - setProjectUIStatus('downloaded'); - addLog('info', `已选择项目目录: ${selectedPath}`); + if (isTauriMode()) { + const { open } = await import('@tauri-apps/plugin-dialog'); + const result = await open({ directory: true, multiple: false, title: '选择项目目录' }); + if (typeof result === 'string') { + setProjectPath(result); + setProjectUIStatus('downloaded'); + addLog('info', `已选择项目目录: ${result}`); + } } } catch (error) { addLog('error', `选择目录失败: ${error instanceof Error ? error.message : String(error)}`); From 74aa05c1ef72c31a2f749ec007b56b72f66e1fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=99=A8?= Date: Wed, 22 Apr 2026 21:02:08 +0800 Subject: [PATCH 6/7] fix(tauri): grant dialog plugin access to remote webview origin The webview loads from http://localhost:8180, which Tauri treats as a remote URL. Capabilities default to local-only, so dialog:allow-open was denied at runtime even though the plugin was registered and the JS call site was correct ("Command plugin:dialog|open not allowed by ACL"). Adding http://localhost:8180/* to the capability's remote.urls whitelist lets the dialog plugin accept invokes from the webview. Co-Authored-By: Claude Opus 4.7 (1M context) --- src-tauri/capabilities/default.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 6ddc4873..9af029fc 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -3,6 +3,9 @@ "identifier": "default", "description": "Default capabilities for Code Agent", "windows": ["main"], + "remote": { + "urls": ["http://localhost:8180/*"] + }, "permissions": [ "core:default", "updater:default", From 2b86847af606f81931289dbf49a659f73fc66315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=99=A8?= Date: Wed, 22 Apr 2026 21:09:20 +0800 Subject: [PATCH 7/7] chore: sync Cargo.lock for tauri-plugin-dialog Co-Authored-By: Claude Opus 4.7 (1M context) --- src-tauri/Cargo.lock | 67 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 8d48dc38..a86f7ee8 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -466,6 +466,7 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-dialog", "tauri-plugin-global-shortcut", "tauri-plugin-single-instance", "tauri-plugin-updater", @@ -3114,6 +3115,30 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + [[package]] name = "ring" version = "0.17.14" @@ -3959,6 +3984,48 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-dialog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1fa4150c95ae391946cc8b8f905ab14797427caba3a8a2f79628e956da91809" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36e1ec28b79f3d0683f4507e1615c36292c0ea6716668770d4396b9b39871ed8" +dependencies = [ + "anyhow", + "dunce", + "glob", + "log", + "objc2-foundation", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", + "url", +] + [[package]] name = "tauri-plugin-global-shortcut" version = "2.3.1"