When the user runs /voice and then presses Ctrl+B in the TUI, three
handlers collaborate to consume the chord and none of them dispatch
voice.record:
- isAction() is platform-aware — on macOS it requires Cmd (meta/super),
so Ctrl+B fails the match in useInputHandlers and never triggers
voiceStart/voiceStop.
- TextInput's Ctrl+B pass-through list doesn't include 'b', so the
keystroke falls through to the wordMod backward-word branch on Linux
and to the printable-char insertion branch on macOS — the latter is
exactly what timmie reported ("enters a b into the tui").
- /voice emits "voice: on" with no hint, so the user has no way to
know Ctrl+B is the recording toggle.
Introduces isVoiceToggleKey(key, ch) in lib/platform.ts that matches
raw Ctrl+B on every platform (mirrors tips.py and config.yaml's
voice.record_key default) and additionally accepts Cmd+B on macOS so
existing muscle memory keeps working. Wires it into useInputHandlers,
adds Ctrl+B to TextInput's pass-through list so the global handler
actually receives the chord, and appends "press Ctrl+B to record" to
the /voice on message.
Empirically verified with hermes --tui: Ctrl+B no longer leaks 'b'
into the composer and now dispatches the voice.record RPC (the
downstream ImportError for hermes_cli.voice is a separate upstream
bug — follow-up patch).
82 lines
3.4 KiB
TypeScript
82 lines
3.4 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const originalPlatform = process.platform
|
|
|
|
async function importPlatform(platform: NodeJS.Platform) {
|
|
vi.resetModules()
|
|
Object.defineProperty(process, 'platform', { value: platform })
|
|
|
|
return import('../lib/platform.js')
|
|
}
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(process, 'platform', { value: originalPlatform })
|
|
vi.resetModules()
|
|
})
|
|
|
|
describe('platform action modifier', () => {
|
|
it('treats kitty Cmd sequences as the macOS action modifier', async () => {
|
|
const { isActionMod } = await importPlatform('darwin')
|
|
|
|
expect(isActionMod({ ctrl: false, meta: false, super: true })).toBe(true)
|
|
expect(isActionMod({ ctrl: false, meta: true, super: false })).toBe(true)
|
|
expect(isActionMod({ ctrl: true, meta: false, super: false })).toBe(false)
|
|
})
|
|
|
|
it('still uses Ctrl as the action modifier on non-macOS', async () => {
|
|
const { isActionMod } = await importPlatform('linux')
|
|
|
|
expect(isActionMod({ ctrl: true, meta: false, super: false })).toBe(true)
|
|
expect(isActionMod({ ctrl: false, meta: false, super: true })).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('isVoiceToggleKey', () => {
|
|
it('matches raw Ctrl+B on macOS (doc-default across platforms)', async () => {
|
|
const { isVoiceToggleKey } = await importPlatform('darwin')
|
|
|
|
expect(isVoiceToggleKey({ ctrl: true, meta: false, super: false }, 'b')).toBe(true)
|
|
expect(isVoiceToggleKey({ ctrl: true, meta: false, super: false }, 'B')).toBe(true)
|
|
})
|
|
|
|
it('matches Cmd+B on macOS (preserve platform muscle memory)', async () => {
|
|
const { isVoiceToggleKey } = await importPlatform('darwin')
|
|
|
|
expect(isVoiceToggleKey({ ctrl: false, meta: true, super: false }, 'b')).toBe(true)
|
|
expect(isVoiceToggleKey({ ctrl: false, meta: false, super: true }, 'b')).toBe(true)
|
|
})
|
|
|
|
it('matches Ctrl+B on non-macOS platforms', async () => {
|
|
const { isVoiceToggleKey } = await importPlatform('linux')
|
|
|
|
expect(isVoiceToggleKey({ ctrl: true, meta: false, super: false }, 'b')).toBe(true)
|
|
})
|
|
|
|
it('does not match unmodified b or other Ctrl combos', async () => {
|
|
const { isVoiceToggleKey } = await importPlatform('darwin')
|
|
|
|
expect(isVoiceToggleKey({ ctrl: false, meta: false, super: false }, 'b')).toBe(false)
|
|
expect(isVoiceToggleKey({ ctrl: true, meta: false, super: false }, 'a')).toBe(false)
|
|
expect(isVoiceToggleKey({ ctrl: true, meta: false, super: false }, 'c')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('isMacActionFallback', () => {
|
|
it('routes raw Ctrl+K and Ctrl+W to readline kill-to-end / delete-word on macOS', async () => {
|
|
const { isMacActionFallback } = await importPlatform('darwin')
|
|
|
|
expect(isMacActionFallback({ ctrl: true, meta: false, super: false }, 'k', 'k')).toBe(true)
|
|
expect(isMacActionFallback({ ctrl: true, meta: false, super: false }, 'w', 'w')).toBe(true)
|
|
// Must not fire when Cmd (meta/super) is held — those are distinct chords.
|
|
expect(isMacActionFallback({ ctrl: true, meta: true, super: false }, 'k', 'k')).toBe(false)
|
|
expect(isMacActionFallback({ ctrl: true, meta: false, super: true }, 'w', 'w')).toBe(false)
|
|
})
|
|
|
|
it('is a no-op on non-macOS (Linux routes Ctrl+K/W through isActionMod directly)', async () => {
|
|
const { isMacActionFallback } = await importPlatform('linux')
|
|
|
|
expect(isMacActionFallback({ ctrl: true, meta: false, super: false }, 'k', 'k')).toBe(false)
|
|
expect(isMacActionFallback({ ctrl: true, meta: false, super: false }, 'w', 'w')).toBe(false)
|
|
})
|
|
})
|