From ff6ae6dc073c896bc05ef13c24e8738f55a93538 Mon Sep 17 00:00:00 2001 From: oratis Date: Thu, 28 May 2026 22:38:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(v0.1.6):=20Bash=20tool=20calls=20always=20r?= =?UTF-8?q?eported=20"error"=20(Rust=20snake=5Fcase=20=E2=86=92=20TS=20cam?= =?UTF-8?q?elCase)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The visible symptom from the 0.1.5 user playtest: a green-exit-code Bash command (`ls -la` on an empty dir) showed a red `✕ error` badge on the tool card. The underlying issue: Rust output structs (`ReadOk`, `EditOk`, `BashOk`) serialized fields in snake_case (`exit_code`, `lines_total`, `diff_preview`). The TS wrappers in mac-tools.ts read them in camelCase (`r.exitCode`, `r.linesTotal`, `r.diffPreview`). serde-rs does not auto-convert, so every field came back undefined on the JS side. For BashOk that meant `r.exitCode !== 0` was `undefined !== 0` == true, so `isError` was always set, so every successful Bash call rendered red. For ReadOk/EditOk it silently dropped the line totals and diff previews shown in the chat. Fix: add `#[serde(rename_all = "camelCase")]` on the three response structs. Input structs (BashInput, EditInput, GrepInput) keep `rename_all = "snake_case"` because the JS side intentionally sends snake_case fields (matches the agent's tool schema). Glob and Grep response structs were single-word fields already, no change. Polish carry-over: - ⌘N (new session) / ⌘, (settings) / ⌘/ (about) keyboard shortcuts via new src/lib/keyboard.ts helper - Switching project now clears agent history + chat (was: old conversation persisted with new cwd, confusing) Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 22 ++++++++++++ apps/cli/package.json | 2 +- apps/desktop/package.json | 2 +- apps/desktop/src-tauri/Cargo.lock | 2 +- apps/desktop/src-tauri/Cargo.toml | 2 +- apps/desktop/src-tauri/src/tools.rs | 3 ++ apps/desktop/src-tauri/tauri.conf.json | 2 +- apps/desktop/src/App.tsx | 22 +++++++++++- apps/desktop/src/lib/keyboard.ts | 48 ++++++++++++++++++++++++++ 9 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 apps/desktop/src/lib/keyboard.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 354036f..9368d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,28 @@ All notable changes to DeepCode are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.6] — 2026-05-28 + +### 🐛 Critical fix — Bash tool calls were always reporting "error" +The Rust output structs (`ReadOk`, `EditOk`, `BashOk`) returned fields +in snake_case (`exit_code`, `lines_total`, `diff_preview`) while the +TS wrappers read them in camelCase. Result: `r.exitCode` was always +`undefined`, so `undefined !== 0` made every Bash tool result render +with a red `✕ error` badge — even when the underlying command had +exit code 0. Read + Edit silently dropped diff previews + line totals +for the same reason. + +Fixed by adding `#[serde(rename_all = "camelCase")]` on the three +output structs. Glob and Grep were already single-word fields, no +change needed. + +### Polish carry-over +- **Keyboard shortcuts**: ⌘N starts a new session, ⌘, opens Settings, + ⌘/ opens About. New `src/lib/keyboard.ts` helper. +- **Switching project now clears chat history** so the next message + runs against the fresh cwd (was: old conversation lingered with + new project context). + ## [0.1.5] — 2026-05-28 ### Polish + dead-code removal diff --git a/apps/cli/package.json b/apps/cli/package.json index c52adf1..73252c4 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "deepcode-cli", - "version": "0.1.5", + "version": "0.1.6", "description": "DeepCode CLI — DeepSeek-powered AI coding agent, parity with Claude Code", "license": "MIT", "type": "module", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 3105c3c..2cc8da3 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@deepcode/desktop", - "version": "0.1.5", + "version": "0.1.6", "private": true, "description": "DeepCode Mac desktop client — Tauri + React", "license": "MIT", diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index a5276d9..98cee64 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -672,7 +672,7 @@ dependencies = [ [[package]] name = "deepcode_desktop" -version = "0.1.4" +version = "0.1.5" dependencies = [ "dirs 5.0.1", "serde", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index a4de589..6cef93e 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deepcode_desktop" -version = "0.1.5" +version = "0.1.6" description = "DeepCode Mac desktop client" authors = ["oratis"] edition = "2021" diff --git a/apps/desktop/src-tauri/src/tools.rs b/apps/desktop/src-tauri/src/tools.rs index 373886a..d48e4bd 100644 --- a/apps/desktop/src-tauri/src/tools.rs +++ b/apps/desktop/src-tauri/src/tools.rs @@ -14,6 +14,7 @@ use tokio::process::Command; // ────────────────────────────────────────────────────────────────────────── #[derive(Serialize)] +#[serde(rename_all = "camelCase")] pub struct ReadOk { pub content: String, pub lines_total: usize, @@ -101,6 +102,7 @@ pub struct EditInput { } #[derive(Serialize)] +#[serde(rename_all = "camelCase")] pub struct EditOk { pub replaced: usize, pub diff_preview: String, @@ -155,6 +157,7 @@ pub struct BashInput { } #[derive(Serialize)] +#[serde(rename_all = "camelCase")] pub struct BashOk { pub stdout: String, pub stderr: String, diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 7e9752a..39a3a7b 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "DeepCode", - "version": "0.1.5", + "version": "0.1.6", "identifier": "dev.deepcode.desktop", "build": { "frontendDist": "../dist", diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index e63e231..a1b3c46 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -7,6 +7,7 @@ import { InspectorRail } from './components/InspectorRail.js'; import { ProjectPickerOverlay } from './components/ProjectPickerOverlay.js'; import { Sidebar } from './components/Sidebar.js'; import { UpdateBanner } from './components/UpdateBanner.js'; +import { registerShortcut } from './lib/keyboard.js'; import { clearHistory as clearAgentHistory } from './lib/mac-agent.js'; import { loadProjectPath, saveProjectPath } from './lib/project.js'; import { onUpdateDownloaded, startUpdaterPolling } from './lib/updater.js'; @@ -36,9 +37,23 @@ export function App(): JSX.Element { const offShim = window.deepcode.onUpdateDownloaded((info) => setUpdate(info)); const offReal = onUpdateDownloaded((info) => setUpdate(info)); startUpdaterPolling(); + + // Global keyboard shortcuts that mirror the sidebar hints. + const offN = registerShortcut('meta+n', () => { + clearAgentHistory(); + setActiveSessionId(null); + setScreen('repl'); + setSessionEpoch((k) => k + 1); + }); + const offComma = registerShortcut('meta+,', () => setScreen('settings')); + const offSlash = registerShortcut('meta+/', () => setScreen('about')); + return () => { offShim(); offReal(); + offN(); + offComma(); + offSlash(); }; }, []); @@ -95,8 +110,13 @@ export function App(): JSX.Element { setSessionEpoch((k) => k + 1); }} onSwitchProject={async () => { - // Force-show the picker again by clearing state. + // Force-show the picker again by clearing state. Also clear + // the in-memory conversation so the next session starts + // fresh in the new project's cwd. + clearAgentHistory(); setProjectPath(null); + setActiveSessionId(null); + setSessionEpoch((k) => k + 1); }} />
diff --git a/apps/desktop/src/lib/keyboard.ts b/apps/desktop/src/lib/keyboard.ts new file mode 100644 index 0000000..1261d8b --- /dev/null +++ b/apps/desktop/src/lib/keyboard.ts @@ -0,0 +1,48 @@ +// Global keyboard shortcut helper. +// React per-component listeners would have to all coordinate; instead we +// expose a single function that hooks the window once at App-mount and +// dispatches by chord. Chords use lowercase letters with modifiers +// prefixed: 'meta+n', 'meta+,', 'meta+.', 'meta+\\'. + +type Handler = (e: KeyboardEvent) => void; + +const handlers = new Map(); + +function eventChord(e: KeyboardEvent): string { + const parts: string[] = []; + if (e.metaKey) parts.push('meta'); + if (e.ctrlKey) parts.push('ctrl'); + if (e.shiftKey) parts.push('shift'); + if (e.altKey) parts.push('alt'); + parts.push(e.key.toLowerCase()); + return parts.join('+'); +} + +let installed = false; +function install(): void { + if (installed) return; + installed = true; + window.addEventListener('keydown', (e) => { + const chord = eventChord(e); + const h = handlers.get(chord); + if (h) { + // Don't preempt the target — composer textarea handles its own keys. + // We only fire global shortcuts when the event target isn't a focused + // input that wants the key. ⌘+letter is rarely consumed by inputs + // (browsers route them out), so a simple metaKey gate is enough. + if (e.metaKey || e.ctrlKey) { + e.preventDefault(); + h(e); + } + } + }); +} + +/** Register a keyboard shortcut. Returns an unbind function. */ +export function registerShortcut(chord: string, handler: Handler): () => void { + install(); + handlers.set(chord, handler); + return () => { + if (handlers.get(chord) === handler) handlers.delete(chord); + }; +}