diff --git a/desktop/src-tauri/capabilities/default.json b/desktop/src-tauri/capabilities/default.json
index 0e039140..b532e822 100644
--- a/desktop/src-tauri/capabilities/default.json
+++ b/desktop/src-tauri/capabilities/default.json
@@ -5,7 +5,10 @@
"windows": ["main"],
"permissions": [
"core:default",
+ "core:window:allow-close",
+ "core:window:allow-minimize",
"core:window:allow-start-dragging",
+ "core:window:allow-toggle-maximize",
"shell:allow-open",
{
"identifier": "shell:allow-execute",
diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs
index 8a15c4f7..7671fce4 100644
--- a/desktop/src-tauri/src/lib.rs
+++ b/desktop/src-tauri/src/lib.rs
@@ -394,12 +394,6 @@ pub fn run() {
// 让 adapter 连上动态端口。
spawn_and_track_adapters_sidecar(&app.handle());
- let _window = app.get_webview_window("main").unwrap();
-
- // Windows: hide native decorations — frontend renders custom titlebar
- #[cfg(target_os = "windows")]
- let _ = _window.set_decorations(false);
-
Ok(())
})
.build(tauri::generate_context!())
diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json
index 75b079a6..57b69a2c 100644
--- a/desktop/src-tauri/tauri.conf.json
+++ b/desktop/src-tauri/tauri.conf.json
@@ -18,8 +18,6 @@
"minWidth": 960,
"minHeight": 640,
"decorations": true,
- "titleBarStyle": "Overlay",
- "hiddenTitle": true,
"transparent": false,
"acceptFirstMouse": true
}
diff --git a/desktop/src-tauri/tauri.macos.conf.json b/desktop/src-tauri/tauri.macos.conf.json
new file mode 100644
index 00000000..9405fbbc
--- /dev/null
+++ b/desktop/src-tauri/tauri.macos.conf.json
@@ -0,0 +1,18 @@
+{
+ "app": {
+ "windows": [
+ {
+ "title": "Claude Code Haha",
+ "width": 1440,
+ "height": 960,
+ "minWidth": 960,
+ "minHeight": 640,
+ "decorations": true,
+ "titleBarStyle": "Overlay",
+ "hiddenTitle": true,
+ "transparent": false,
+ "acceptFirstMouse": true
+ }
+ ]
+ }
+}
diff --git a/desktop/src-tauri/tauri.windows.conf.json b/desktop/src-tauri/tauri.windows.conf.json
new file mode 100644
index 00000000..ba908a16
--- /dev/null
+++ b/desktop/src-tauri/tauri.windows.conf.json
@@ -0,0 +1,16 @@
+{
+ "app": {
+ "windows": [
+ {
+ "title": "Claude Code Haha",
+ "width": 1440,
+ "height": 960,
+ "minWidth": 960,
+ "minHeight": 640,
+ "decorations": false,
+ "transparent": false,
+ "acceptFirstMouse": true
+ }
+ ]
+ }
+}
diff --git a/desktop/src/components/chat/MessageList.test.tsx b/desktop/src/components/chat/MessageList.test.tsx
index 603ee9a2..ca1e83d2 100644
--- a/desktop/src/components/chat/MessageList.test.tsx
+++ b/desktop/src/components/chat/MessageList.test.tsx
@@ -281,4 +281,32 @@ describe('MessageList nested tool calls', () => {
'先看 CLI 和服务端入口。\n再看 desktop 前后端边界。'
)
})
+
+ it('shows raw startup details under translated CLI startup errors', () => {
+ useChatStore.setState({
+ sessions: {
+ [ACTIVE_TAB]: makeSessionState({
+ messages: [
+ {
+ id: 'error-1',
+ type: 'error',
+ code: 'CLI_START_FAILED',
+ message:
+ 'CLI exited during startup (code 1): Claude Code on Windows requires git-bash (https://git-scm.com/downloads/win).',
+ timestamp: 1,
+ },
+ ],
+ }),
+ },
+ })
+
+ render()
+
+ expect(screen.getByText('Failed to start CLI process.')).toBeTruthy()
+ expect(
+ screen.getByText(
+ 'CLI exited during startup (code 1): Claude Code on Windows requires git-bash (https://git-scm.com/downloads/win).',
+ ),
+ ).toBeTruthy()
+ })
})
diff --git a/desktop/src/components/chat/MessageList.tsx b/desktop/src/components/chat/MessageList.tsx
index 4ee13c6b..a50286ea 100644
--- a/desktop/src/components/chat/MessageList.tsx
+++ b/desktop/src/components/chat/MessageList.tsx
@@ -222,9 +222,18 @@ export const MessageBlock = memo(function MessageBlock({
const errorKey = message.code ? `error.${message.code}` as TranslationKey : null
const errorText = errorKey ? t(errorKey) : null
const displayMessage = (errorText && errorText !== errorKey) ? errorText : message.message
+ const showRawDetail =
+ Boolean(message.message) &&
+ message.message.trim() !== '' &&
+ message.message !== displayMessage
return (
Error: {displayMessage}
+ {showRawDetail && (
+
+ {message.message}
+
+ )}
)
}
diff --git a/desktop/src/components/layout/WindowControls.test.tsx b/desktop/src/components/layout/WindowControls.test.tsx
new file mode 100644
index 00000000..cf29ada5
--- /dev/null
+++ b/desktop/src/components/layout/WindowControls.test.tsx
@@ -0,0 +1,69 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import { fireEvent, render, screen, waitFor } from '@testing-library/react'
+import '@testing-library/jest-dom'
+
+const minimize = vi.fn().mockResolvedValue(undefined)
+const toggleMaximize = vi.fn().mockResolvedValue(undefined)
+const close = vi.fn().mockResolvedValue(undefined)
+const isMaximized = vi.fn().mockResolvedValue(false)
+const onResized = vi.fn().mockResolvedValue(() => {})
+
+vi.mock('@tauri-apps/api/window', () => ({
+ getCurrentWindow: () => ({
+ minimize,
+ toggleMaximize,
+ close,
+ isMaximized,
+ onResized,
+ }),
+}))
+
+describe('WindowControls', () => {
+ const originalPlatform = navigator.platform
+
+ beforeEach(async () => {
+ minimize.mockClear()
+ toggleMaximize.mockClear()
+ close.mockClear()
+ isMaximized.mockClear()
+ onResized.mockClear()
+
+ Object.defineProperty(window, '__TAURI_INTERNALS__', {
+ configurable: true,
+ value: {},
+ })
+ Object.defineProperty(navigator, 'platform', {
+ configurable: true,
+ value: 'Win32',
+ })
+ vi.resetModules()
+ })
+
+ afterEach(() => {
+ Reflect.deleteProperty(window, '__TAURI_INTERNALS__')
+ Object.defineProperty(navigator, 'platform', {
+ configurable: true,
+ value: originalPlatform,
+ })
+ })
+
+ it('invokes Tauri window APIs for custom controls on Windows', async () => {
+ const { WindowControls } = await import('./WindowControls')
+
+ render()
+
+ await waitFor(() => {
+ expect(screen.getByRole('button', { name: 'Minimize window' })).toBeInTheDocument()
+ })
+
+ fireEvent.click(screen.getByRole('button', { name: 'Minimize window' }))
+ fireEvent.click(screen.getByRole('button', { name: 'Maximize window' }))
+ fireEvent.click(screen.getByRole('button', { name: 'Close window' }))
+
+ await waitFor(() => {
+ expect(minimize).toHaveBeenCalledTimes(1)
+ expect(toggleMaximize).toHaveBeenCalledTimes(1)
+ expect(close).toHaveBeenCalledTimes(1)
+ })
+ })
+})
diff --git a/desktop/src/components/layout/WindowControls.tsx b/desktop/src/components/layout/WindowControls.tsx
index 35f4706d..2825e80a 100644
--- a/desktop/src/components/layout/WindowControls.tsx
+++ b/desktop/src/components/layout/WindowControls.tsx
@@ -34,13 +34,20 @@ export function WindowControls() {
return () => { unlisten?.() }
}, [])
+ const runWindowAction = (action: () => Promise) => {
+ void action().catch((error) => {
+ console.error('Window control action failed', error)
+ })
+ }
+
if (!showWindowControls || !win) return null
return (
{/* Minimize */}