cc-haha/desktop/src/hooks/useKeyboardShortcuts.ts
程序员阿江(Relakkes) 993b96cd39 Stabilize the desktop transcript so long agent sessions stay readable
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)
2026-04-06 20:37:44 +08:00

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])
}