feat(hotkey): 会话激活期间 Esc 由 OpenLess 独占——不再透传宿主应用 - #855
Conversation
PR Reviewer Guide 🔍(Review updated until commit cee2e66)Here are some key observations to aid the review process:
|
|
Persistent review updated to latest commit 5323d42 |
胶囊转圈时按 Esc,取消会话的同时宿主应用也收到了这个键——一次按键双重 生效(如顺带取消了 Claude 正在生成的回复)。参照输入法的交互模型:组合窗 激活时 Esc 只取消候选词、宿主应用收不到;胶囊显示进行中的会话时同理, Esc 的语义就是「取消它」,应当被独占消费。 实现:hotkey.rs 新增进程级 ESC_EXCLUSIVE 旗标,由 emit_capsule(所有会话 状态变化的单一出口)维护——胶囊为 Recording/Transcribing/Polishing 且 dictation phase 非 Idle 时置位,终止帧清除。phase 条件排除 QA 会话:QA 的 Esc 由聚焦的浮窗窗口处理,吞键反而会挡掉。置位期间 macOS active tap 对 Esc keydown 返回 null 删除事件,Windows 底层钩子返回 LRESULT(1)—— Windows 本就吞听写触发键,Esc 是补齐同一语义。keyup 不吞:宿主应用几乎 都在 keydown 响应 Esc,孤儿 keyup 无害。 依赖 Open-Less#853(独立取消通道):没有它,Processing 期间吞掉的 Esc 无法真正 触发取消,会变成「吃掉按键但什么都不做」。 Co-Authored-By: Claude Fable 5 <[email protected]>
5323d42 to
77cbe7c
Compare
|
Persistent review updated to latest commit 77cbe7c |
- 提取 esc_exclusive_for_capsule(state, phase) 纯函数,emit_capsule 调用 - 表格测试覆盖:进行中胶囊非 Idle 独占、终止帧清除、QA(phase=Idle)不独占
|
Persistent review updated to latest commit 2cab0c0 |
- maybe_request_approval 增加取消感知:会话取消(Esc)时按 Deny 处理并清理 审批注册表,避免审批挂起期间 Esc 被独占吞掉却毫无效果(PR Open-Less#855 场景) - run_voice_agent_transcript 审批后若会话已取消,强制结果为 Cancelled, 避免把第一轮拦截文本当 Done 收尾 - schedule_capsule_idle 记录触发时胶囊状态,到点时若期间有更新的 emit 则 跳过隐藏(旧 schedule 不再覆盖新状态,消除取消路径双 emit 竞争) - 补测试:审批取消感知、schedule 新旧状态竞争
|
Persistent review updated to latest commit cee2e66 |
User description
问题
胶囊转圈(录音/转写/润色)时按 Esc,OpenLess 取消会话的同时,宿主应用也收到了这个 Esc——一次按键双重生效。典型场景:在 Claude 里边对话边听写,按 Esc 想取消转写,结果顺带把 Claude 正在生成的回复也取消了。
交互模型
参照输入法:组合窗激活时按 Esc,取消的是候选词,宿主应用收不到这个键——因为此刻用户的意图对象显然是输入法。胶囊显示进行中的会话时完全同构:Esc 的语义就是「取消这个会话」,应当被独占消费。一次输入触发两个语义(且第二个是破坏性的)违反基本交互原则。
Windows 底层钩子本来就吞掉听写触发键(返回
LRESULT(1)),唯独 Esc 一直放行——本 PR 是把同一语义补齐到 Esc,并给 macOS 补上对应能力(active tap 返回 null 删除事件)。实现
hotkey.rs:进程级ESC_EXCLUSIVE旗标 +set_esc_exclusive()。置位期间 macOS tap 对 Esc keydown 返回 null,Windows 钩子返回LRESULT(1)。keyup 不吞(宿主应用几乎都在 keydown 响应 Esc,孤儿 keyup 无害)。capsule_focus.rs:在emit_capsule(所有会话状态变化的单一出口,含 fix(capsule): 终止态 2s 自动隐藏 — 覆盖 Done/Cancelled/Error 全路径 #77 审计保证的全部终止路径)维护旗标——胶囊为Recording/Transcribing/Polishing且 dictation phase 非 Idle 时置位,终止帧(Done/Cancelled/Error/Idle)自然清除。验证
cargo check --tests干净;cargo test794 passed。🤖 Generated with Claude Code
PR Type
Bug fix, Enhancement, Tests
Description
Add process-wide
ESC_EXCLUSIVEflag, maintained byemit_capsule.macOS tap and Windows hook consume Esc during active dictation.
Exclude QA sessions; Esc remains owned by focused floating panel.
Harden capsule idle scheduling and approval cancellation races.
Diagram Walkthrough
flowchart LR Esc["Esc keydown"] --> Listener["Platform listener (macOS tap / Windows hook)"] Listener --> Check{"esc_exclusive?"} Check -- "yes" --> Consume["Return null / LRESULT(1)"] Check -- "no" --> Host["Pass to host app"] emit_capsule["emit_capsule state change"] --> Flag["set_esc_exclusive"] Flag --> CheckFile Walkthrough
coordinator.rs
Guard capsule idle timers and approval cancellationopenless-all/app/src-tauri/src/coordinator.rs
schedule_capsule_idleto skip stale timers when a newer stateis emitted.
Cancelledwhen session is cancelled afterapproval wait.
approval registry.
dictation.rs
Handle cancellation during approval waitopenless-all/app/src-tauri/src/coordinator/dictation.rs
Cancelledwhen cancelled flag is set afterapproval.
tokio::select!to abort approval wait on processing cancel.capsule_focus.rs
Maintain Esc exclusivity in capsule state emitopenless-all/app/src-tauri/src/coordinator/capsule_focus.rs
esc_exclusive_for_capsulefor Esc exclusivitydecisions.
ESC_EXCLUSIVEflag inemit_capsulebased on capsule state anddictation phase.
hotkey.rs
Consume Esc in platform listeners when exclusiveopenless-all/app/src-tauri/src/hotkey.rs
ESC_EXCLUSIVEatomic flag andset_esc_exclusivehelper.
LRESULT(1)for Esc keydown whenexclusive.
hotkey.rs
Add mobile stub for Esc exclusivityopenless-all/app/src-tauri/src/mobile_stubs/hotkey.rs
set_esc_exclusivestub for mobile platforms.