docs(v2): C28 Keybindings、Vim 模式与 Voice 输入 (YAO-127) - #65
Merged
Conversation
新增 docs/31-Keybindings-Vim-与-Voice-输入.md,按 V2-REVISION-SPEC §0.5.4 新章规则写成叙事博客而非参考手册:先讲为什么三个输入子系统值得放在一起, 然后分三段——Keybindings(18 上下文 / 默认表 / Ink 修饰键归一化 / chord 解析 / 用户覆盖闸)、Vim(11 状态变体的 DAG / count 上限 / dot-repeat / Escape 有意不走 Keybindings 的设计)、Voice(双闸 / 三档录音回落 / WebSocket 心跳 与 finalize / 按住空格的时序状态机 / 20 语言归一化)。 CI 闸(本地预跑全绿): - C-3 代码块占比 1.2% ≤ 25% - C-4 26 个标题全部合规 - C-5 无 frontmatter - lint-no-fuzzy-quantifiers 无禁词 - lint-no-spec-jargon 无 squad 内部术语 - check-source-commits 全章指向 290fdc94 - gen-module-matrix --check-orphans 通过 风格双亲:v1-03 状态管理 + v1-21 Ink 框架深度定制(详见 PR 描述)。 Co-authored-by: multica-agent <github@multica.ai>
- 上下文清单与源码 schema.ts:12-32 对齐为 18 个 - 动作 ID 改为 app:exit / voice:pushToTalk 等 - MODE_CYCLE_KEY 改为 VT-模式判定(非 macOS 区分) - resolver / false 协议 / invokeAction 按源码重写 - dot-repeat 修正 INSERT 路径 - FinalizeSource 改为 5 分支 - 语言列表改为 20 个 base code - 增补附:源码引用清单 Co-authored-by: multica-agent <github@multica.ai>
- L97 重写:举例改为合法 context('Chat' / 'Autocomplete' / 'Global'),
明确 uniqueContexts 只用于构 Set 过滤 bindings,不是按数组顺序找第一个
匹配;胜负仍由 bindings 数组的 last-wins 决定,对齐 resolver.ts:192-228。
- 删除 ## 附:本章源码引用清单 整节:V2-REVISION-SPEC.md:231 + §0.5.5
明确禁止此类脚手架外漏到正文,CI lint-no-spec-jargon-in-prose 也会 fail。
- 把原附录里的 commands/{vim,voice,keybindings}/ 与 hooks/useVoiceEnabled.ts
以叙事方式补回正文,确保 required anchors 仍在正文出现。
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
摘要
新增 docs/31-Keybindings-Vim-与-Voice-输入.md(C28),按 V2-REVISION-SPEC §0.5.4 新章规则写成叙事博客而非参考手册。
三段结构:
useKeybinding的 false 协议与 stopImmediatePropagation、用户自定义被tengu_keybinding_customization_release守、reservedShortcuts 三档不可绑定键。VimStateINSERT/NORMAL 双外层 +CommandState11 变体内层状态机、3dw/di(/df,三类典型流的状态转移、MAX_VIM_COUNT = 10000截断、RecordedChangeunion 支撑.dot-repeat、Escape 故意不走 Keybindings 而是写死在useVimInput.ts:189-195的设计原因。tengu_amber_quartz_disabled反向 kill switch + 鉴权双闸、cpal → arecord → SoX rec 三档录音回落(含linuxHasAlsaCards早返与 150msprobeArecordrace)、WebSocket 心跳 8s + finalize 双计时器(noData 1500 / safety 5000)、FinalizeSourceunion 四档分桶、hooks/useVoice.ts四常数(RELEASE 200 / FIRST_PRESS_FALLBACK 2000 / REPEAT_FALLBACK 600 / FOCUS_SILENCE 5000)压"按住空格"交互、20 BCP-47 语言归一化到en兜底。本地 CI 闸结果
Manifest diff 摘要
无 manifest 变更:本章只新增
docs/31-Keybindings-Vim-与-Voice-输入.md一个文件;scripts/check-code-ratio.ts的NEW_CHAPTER_FILES由 YAO-140 预先登记,本 PR 不动 scripts/。风格双亲实证
风格双亲:v1-03 状态管理 — React 与非 React 世界的状态桥接 + v1-21 Ink 框架深度定制 — 在终端中运行 React
v1 原文摘抄(≥ 200 字 × 2 段)
【摘抄 1,来自 docs/03-状态管理.md "为什么状态管理值得单独一篇?"】
Claude Code 面临一个独特的状态管理难题:它既是一个 React 应用,又不完全是。
终端 UI 用 Ink(React for CLI)渲染,组件需要响应式的状态更新。但核心业务逻辑 —— API 调用、工具执行、Agent 编排 —— 运行在 React 树之外。一次工具调用的结果需要同时:
query.ts对话循环读取如果用 Redux/Zustand 这类库?太重了。React 内置的
useState/useReducer?无法从 React 树外部访问。模块级全局变量?无法触发 React 重渲染。Claude Code 的答案是:三层状态架构 + 一个 35 行的自研 Store。
【摘抄 2,来自 docs/21-Ink框架深度定制.md "为什么要 Fork Ink?"】
Ink 是一个开源框架,让你在终端中使用 React 组件编写 UI。官方 Ink 适合简单的 CLI 工具 —— 但 Claude Code 不是简单的 CLI。它需要:
官方 Ink 不支持这些。Claude Code 团队 fork 了 Ink 并进行了大量深度定制,最终形成了一个功能完备的终端 React 渲染引擎。
本章新写正文摘抄(≥ 200 字 × 2 段,覆盖典型叙事段)
【新写 1,来自 docs/31-Keybindings-Vim-与-Voice-输入.md "为什么要把这三块放在同一篇里讲?"】
当你打开终端,敲下
claude,REPL 起来之后你按下任意一个键,会发生什么?这背后其实不只一条路。Ink 把原始按键事件喂给上层;上层要决定的,是把这个按键当成"用户输入的字符"喂进编辑框,还是当成"快捷键"派给某个动作,还是当成"我此刻按住空格在录音"喂给麦克风。三种解读模式共用同一条 Ink 输入流,又各自维护一套自己的状态——这就是本章想要拆开看的"输入层"。放在书脊上看,C26 讲了 Ink 的渲染与组件系统,C27 讲了多模态文件上传,C28 这一篇要补的是介于"原始按键"与"高层意图"之间的那一层胶水:默认快捷键怎么注册、用户怎么覆盖、Vim 模式怎么用一套状态机把单字符串成命令、Voice 又怎么把"按住一个键"翻译成一段 16 kHz 的 PCM 流并推给后端做 STT。三套子系统目录是分开的(
keybindings/、vim/、voice/+services/voice*+hooks/useVoice*),但它们的共同点是:都坐在 Ink 的useInput之上,又都要绕开 Ink 默认的"按一下出一个字符"的语义。【新写 2,来自 docs/31-Keybindings-Vim-与-Voice-输入.md "为什么 Vim 这一段值得单独讲?"】
终端里的输入框做 Vim 模式,有两种典型做法。一种是 textarea 加一组 keymap,按下哪个键执行哪段代码;这种做法在做到
i/a/x这种单字符命令时还行,做到3dw/gg/cit这种带计数、带 motion、带 text object 的复合命令时就会变成一团乱麻。另一种做法是显式建一个状态机,把"我按到一半"这件事变成一个一等的"中间状态",让每一次按键都是一次状态转移。Claude Code 走的是后一条路。
vim/types.ts顶部的CommandState是一个 11 个变体的 discriminated union(types.ts:59-75):idle、count、operator、operatorCount、operatorFind、operatorTextObj、find、g、operatorG、replace、indent。读者读到这里可以先把这 11 个名字记下来,下面会把每一个名字在什么时候出现讲清楚。整套 Vim 子系统挂在外层一个更简单的状态变量上:VimState只有INSERT和NORMAL两个值(types.ts:49-51)。INSERT 里没有任何状态机,就是普通的文本输入;NORMAL 里挂着上面那个 11 变体的子状态机。模式之间靠Escape与i/a/o等键切换。Closes YAO-127。不要 merge——尧哥手动合。