mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-19 13:33:35 +08:00
The desktop app now keeps the composer stable while turns are active, reduces low-signal tool noise in the transcript, restores project context under the composer after session creation, and relies on the CLI's own permission requests instead of injecting broader desktop-side Bash asks. This also brings in the supporting desktop app source tree and the server routes/session metadata needed for git info, filesystem browsing, session resume, slash commands, and SDK-backed permission bridging so the UI can operate as a coherent feature instead of a partial patch. Constraint: Desktop transcript needs to stay usable during long multi-tool sessions without hiding file-change diffs Constraint: Permission prompts must mirror CLI behavior closely enough that read-only commands do not get desktop-only prompts Rejected: Keep rendering Read/Bash bodies inline | too noisy and unlike the intended transcript model Rejected: Commit only the touched desktop files | would leave the newly introduced desktop app incomplete in git history Confidence: medium Scope-risk: broad Reversibility: messy Directive: Treat non-writing tools as summary-first transcript events; do not re-expand them by default without validating the UX against long sessions Tested: cd desktop && bun run lint Tested: cd desktop && bun run test -- --run Tested: bun test src/server/__tests__/conversations.test.ts Not-tested: Manual visual regression against the exact screenshots in a live desktop session Not-tested: Full root TypeScript check (repository still has unrelated extracted-native parse failures)
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
import { useEffect } from 'react'
|
|
import { useSessionStore } from '../stores/sessionStore'
|
|
import { useChatStore } from '../stores/chatStore'
|
|
import { useUIStore } from '../stores/uiStore'
|
|
|
|
export function useKeyboardShortcuts() {
|
|
const setActiveSession = useSessionStore((s) => s.setActiveSession)
|
|
const setActiveView = useUIStore((s) => s.setActiveView)
|
|
const closeModal = useUIStore((s) => s.closeModal)
|
|
const activeModal = useUIStore((s) => s.activeModal)
|
|
const stopGeneration = useChatStore((s) => s.stopGeneration)
|
|
const chatState = useChatStore((s) => s.chatState)
|
|
|
|
useEffect(() => {
|
|
const handler = (e: KeyboardEvent) => {
|
|
const meta = e.metaKey || e.ctrlKey
|
|
|
|
// Cmd+N — New session
|
|
if (meta && e.key === 'n') {
|
|
e.preventDefault()
|
|
setActiveSession(null)
|
|
setActiveView('code')
|
|
}
|
|
|
|
// Cmd+K — Focus search (sidebar search input)
|
|
if (meta && e.key === 'k') {
|
|
e.preventDefault()
|
|
const searchInput = document.querySelector('aside input[type="text"]') as HTMLInputElement
|
|
searchInput?.focus()
|
|
}
|
|
|
|
// Escape — Close modal or clear state
|
|
if (e.key === 'Escape') {
|
|
if (activeModal) {
|
|
closeModal()
|
|
}
|
|
}
|
|
|
|
// Cmd+. — Stop generation
|
|
if (meta && e.key === '.') {
|
|
if (chatState !== 'idle') {
|
|
e.preventDefault()
|
|
stopGeneration()
|
|
}
|
|
}
|
|
}
|
|
|
|
document.addEventListener('keydown', handler)
|
|
return () => document.removeEventListener('keydown', handler)
|
|
}, [activeModal, chatState, closeModal, setActiveSession, setActiveView, stopGeneration])
|
|
}
|