Skip to content

fix(tuiv2): 修复浮层回车不关闭/多行错位,完成 Phase 9 Command Prompt 与交互增强体系#718

Merged
wynxing merged 4 commits into
1024XEngineer:mainfrom
pionxe:main
Jun 16, 2026
Merged

fix(tuiv2): 修复浮层回车不关闭/多行错位,完成 Phase 9 Command Prompt 与交互增强体系#718
wynxing merged 4 commits into
1024XEngineer:mainfrom
pionxe:main

Conversation

@pionxe

@pionxe pionxe commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

概述

本 PR 在 TUI v2(neocode-tuiv2 独立二进制)落地 Phase 9:Command Prompt 组件及其交互增强体系(三层键位 / 命令面板 / 会话·模型选择器 / 危险确认弹窗 / 鼠标),并随后修复了联调阶段发现的一组交互缺陷。

  • 基线main 领先 origin/main 3 个提交,工作树干净。
  • 包含提交
    • 4281377d 实现交互增强与视觉打磨 — 键位/面板/浮层/会话/鼠标/命令 (Phase 9)
    • 9d0b937a 补全 Phase 9 交互增强 — Leader 键/模型选择器/确认弹窗/鼠标/命令路由
    • a6f590a4 修复浮层回车不关闭与多行渲染错位等交互问题(本轮重点)
  • 所有变更在 Fake Gateway 后端可独立运行、可验证;真实 Gateway 留待 Phase 20。

🔧 本轮核心修复:浮层组件持有过期 state 指针

现象:命令面板里用方向键移到 /mode 后按回车,面板不关闭、高亮跳回第一项 /model;会话/模型选择器、确认弹窗同样「回车无响应」。

根因state.Reduce 每次返回新的 *ViewStatea.state 随之替换;而 bindComponents() 此前只重新绑定 4 个静态组件(status/stream/prompt/inspector),遗漏了 palette / help / sessionPicker / modelPicker / confirmOverlay 等浮层组件。这些浮层仍持有旧 state 指针,按键改在废弃状态上,当前 state.Overlay.Active 始终不变 → 回车不关闭、选中复位。

修复bindComponents() 绑定全部子组件,根除问题(app.go)。

配套修复(同一提交)

# 修复点 文件
1 命令面板匹配由模糊匹配改为「精确名 → 前缀 → 子串」三级确定性匹配,避免 mode 因评分命中 /model 而非 /mode移除 sahilm/fuzzy 依赖 palette.go
2 命令面板空格键改为「执行选中项」,与会话选择器一致;底部提示更新为 ⏎ / ␣ : execute palette.go
3 确认弹窗确认/取消时调用新增的 closeOverlay() 重置 Overlay.Active,修复回车不关闭 app.go
4 会话切换:sessionSwitchedMsg 重载历史(会清空 Stream)之后追加 Switched to session: <title> 提示 app.go
5 行为流多行消息:续行缩进对齐到首行标签(you/neo)宽度,修复第二行起错位 stream.go
6 输入提示符 → ASCII >:前者是东亚宽度「歧义」字符,CJK 终端下被渲染成双宽,导致首行前缀与续行缩进列数不一致 prompt.go
7 新增换行键 Ctrl+J(Shift+Enter 在 bubbletea v1.3.10 中与 Enter 不可区分),帮助文案更新为 Alt+Enter / Ctrl+J prompt.gokeys.go

变更说明(功能全景)

1. Command Prompt 组件(Phase 9 核心)components/prompt.go

InputState.Mode 渲染三种内联模式并路由按键:

模式 符号 行为
message/command >(Accent) 单/多行输入,Enter 发送、Alt+Enter / Ctrl+J 换行;/ 开头走 Slash 命令
permission_response (Warning) y/n/d/a 单键响应,无需 Enter;Esc 取消
question_answer · 编号选项,长文本换行对齐到选项文本起始处;数字/回车提交

配套:光标闪烁(500ms)/ 左右移动 / Home·End(按 rune,中文无错位);输入历史 Normal Mode Up/Down;底部状态行 [input]/[normal]/[leader] + 会话名 + 模型名。闪烁状态存于中央 ViewState,组件重建不丢失。

2. 三层键位系统 keymap/keys.go

Input / Normal / Leader 三层 Action 映射,由 app.go 统一派发;Leader 1s 超时回退 Normal;FullHelp() 分组供帮助浮层。

3. 命令面板 components/palette.go

Ctrl+P/ 打开,确定性三级匹配;Up/Downj/k 选择、Enter/Space 执行、Esc 关闭;支持鼠标滚轮与点击。内置 /model /mode /session /compact /checkpoint /skills /help /exit

4. 浮层与选择器

会话选择器(切换/d 删除经确认弹窗)、模型选择器(切换并回写 ActiveModel)、危险确认弹窗(Y/Enter 确认、N/Esc/Ctrl+C 取消)、帮助浮层(分组键位)。

5. App 装配与命令路由 app.go

组件动作 → Gateway RPC 管线(submitMessageCmd/resolvePermissionCmd/answerQuestionCmd/loadSessionCmd/deleteSessionCmd/createSessionCmd);Ctrl+C 双退保护;鼠标分发(浮层优先,主视图滚轮控 Stream);浮层互斥 + 统一 Esc 逃生。

变更文件清单(三个提交合计,本轮修复项加粗)

internal/tuiv2/app.go                          绑定全部组件 / closeOverlay / 切换提示(本轮)
internal/tuiv2/app_overlay_test.go  +285 NEW   浮层交互 + 过期指针回归测试(本轮)
internal/tuiv2/components/prompt.go            >符号 / Ctrl+J / 续行缩进(本轮)
internal/tuiv2/components/prompt_test.go  +37  多行渲染回归(本轮)
internal/tuiv2/components/palette.go           三级匹配 / 空格执行 / 移除 fuzzy(本轮)
internal/tuiv2/components/stream.go            多行消息续行对齐(本轮)
internal/tuiv2/components/stream_test.go  +32  多行回归(本轮)
internal/tuiv2/keymap/keys.go                  换行键 Alt+Enter / Ctrl+J(本轮)
internal/tuiv2/components/{model_picker,session_picker}.go  微调
internal/tuiv2/components/{inspector,help}.go +status_bar_test.go
internal/tuiv2/state/{viewstate,reducer}.go
internal/tuiv2/theme/styles.go + gateway/events.go
internal/tuiv2/fakegateway/{fixtures,scenarios}.go   扩充 tool_approval/ask_user/cancel_running 等
cmd/neocode-tuiv2/main.go                      入口接线

如何手工验证

入口:go run ./cmd/neocode-tuiv2(默认 --backend=fake --scenario=default);建议加 --debug 看底部调试行。

0. 构建与测试(前置)

go build ./...
go test  ./internal/tuiv2/... -count=1
gofmt -l cmd internal

预期:全绿、无格式化遗留。

1. 消息模式(默认场景)

go run ./cmd/neocode-tuiv2 --scenario=default
  • 输入文本 Enter → 进入行为流并清空;提示符为 ASCII >
  • 第一行Alt+Enter(或 Ctrl+J)换行 → 第二行Enter 多行整段提交。
  • 输入 /help Enter → 打开帮助浮层。
  • 光标在中间插入中文应正确(如 a中b);光标约每 0.5s 闪烁。

2. 权限确认模式(单键响应)

go run ./cmd/neocode-tuiv2 --scenario=tool_approval
  • 出现 tool.* 请求 …、快捷栏 [Y] 允许 [n] 拒绝 [d] 查看 diff [a] 允许全部
  • y(无 Enter)→ allowed;n → denied;Esc → 取消并回消息模式。

3. ask_user 问答模式(长选项换行对齐)

go run ./cmd/neocode-tuiv2 --scenario=ask_user
  • 窄终端下长选项第二行对齐选项文本起始列、无超宽行;输入数字 Enter 提交;Esc 取消。

4. 输入历史(Normal Mode)

发两条消息 → Esc 进 Normal → Up/Up/Down 浏览,越过尾部清空。

5. 底部状态行

切换模式确认 [input]/[normal]/[leader],右侧会话名 + 模型名。

6. Leader 键

EscSpace(瞬时 [leader],1s 超时回退)→ 验证 h/m/f/c/p/s/n/q

7. ⭐ 命令面板(本轮修复重点)

go run ./cmd/neocode-tuiv2 --scenario=default
  • Ctrl+P 打开,输入 mode高亮应停在 /mode 而非 /model(修复前会错跳)。
  • 方向键移到 /mode,按 Enter 或空格 → 面板立即关闭并切到 build/plan(修复前不关闭、跳回 /model)。
  • Esc 关闭;鼠标滚轮翻页、点击执行。

8. 会话 / 模型选择器与确认弹窗

go run ./cmd/neocode-tuiv2 --scenario=many_sessions
  • Space s 打开会话选择器,选条目 → 切换,流内出现 Switched to session: <title>(修复前无提示)。
  • 某项按 d → 红色确认弹窗 → Y 删除 / N 取消,弹窗均正确关闭(修复前回车不关)。
  • Space p→选 /model(或 /model)打开模型选择器,切换后状态行右侧模型名更新。

9. 多行消息对齐(本轮修复)

  • 让 Agent 输出或用户输入多行长文本:行为流中正文第二行起应对齐到 you/neo 标签宽度,不再左侧塌缩错位。

10. 鼠标滚动 / Ctrl+C / 自适应

  • --scenario=long_output:滚轮上停自动滚动、到底恢复。
  • --scenario=cancel_running:运行中 Ctrl+C 取消;空闲双击 2s 内退出。
  • --scenario=small_terminal:缩放窗口,Prompt 宽自适应、Inspector 按断点显隐、无残留。

测试

范围 结果
go build ./... ✅ 通过
go test ./internal/tuiv2/... -count=1 ✅ 全部通过(tuiv2 / components / keymap / state / theme / gateway / fakegateway)
本轮新增 app_overlay_test.go(+285)含「模拟事件流复现过期指针」回归;prompt_test.go(+37)、stream_test.go(+32) 多行渲染回归

Phase 9 验收标准对照(8/8)

  • 三种模式视觉区分(符号 / 颜色 / 布局不同)
  • 消息模式 Enter 发送、换行(Alt+Enter / Ctrl+J)
  • 权限模式 y/n/d/a 单键响应
  • ask_user 长选项自动换行对齐
  • 底部状态行显示键位模式与会话信息
  • 输入历史 Normal Mode Up/Down
  • 光标闪烁与左右移动
  • go test ./internal/tuiv2/components/ 覆盖三模式输入处理

已知限制 / 后续

  • 三个提交合并携带了 Phase 10–13、16 的交互实现;Phase 9 自身增量是 Prompt 组件 + app 接线,后续可按 Phase 拆分提交。
  • 问答符号 · 当前用 Accent 色(文档原定 Info 色),纯视觉细节,留待打磨阶段。
  • --backend=gateway 真实后端、Checkpoint/Skills 面板项为占位,留待后续 Phase。

Breaking Changes

无。仅新增独立二进制 cmd/neocode-tuiv2internal/tuiv2 包;本轮移除对 sahilm/fuzzy 的运行时依赖,不影响 v1 主链路。


Close #708

pionxe and others added 3 commits June 16, 2026 16:01
实现设计文档 Phase 3(交互增强)和 Phase 4(打磨)的全部功能:

Step 9a - 键位系统重构与 Ctrl+C 双退保护:
- 新增 keymap 包,实现 Input/Normal/Leader 三层键位抽象
- Leader Key 状态机:Space 进入等待,1 秒超时回退 Normal
- Ctrl+C 双退保护:运行中取消 Agent,空闲时双击退出
- 运行取消:调用 client.CancelRun() RPC
- Normal Mode 增强:Ctrl+D/Ctrl+U 半页滚动
- 修复 stream.go 重复 doc comment

Step 9b - 命令面板、帮助浮层与会话管理:
- 实现 Telescope 风格命令面板 (Ctrl+P / Space p)
- 实现分组快捷键帮助浮层 (? / Space h)
- 实现会话切换器 (Space s)
- ViewState 新增 OverlayState 浮层状态
- Slash 命令拦截 (/exit, /help, /session, /clear)
- 会话切换 RPC 调用 (loadSessionCmd)

Step 9c - 角色感知渲染与视觉打磨:
- 用户消息显示 you 标签 (Cyan),助手消息显示 neo 标签 (Blue)
- Reducer 自动注入 role: assistant 到 Agent 消息
- Inspector 新增 Active Tools 和 Files 区块
- 使用 DiffAdd/DiffDel 主题色显示文件变更
- 新增 theme.InfoStyle() 访问器
- 补充 status_bar_test.go 和 inspector_test.go
- 删除 fakegateway/.gitkeep

Step 9d - 鼠标支持与 Slash 命令:
- 启用 tea.WithMouseCellMotion() 鼠标支持
- 鼠标滚轮在 Stream 区域滚动
- 浮层内鼠标事件路由到对应组件
- Slash 命令模式 (Prompt 组件自动拦截 / 开头输入)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
自审发现 22 项设计规范差距,本次修复 P0+P1 共 16 项:

P0 修复(规范强制要求):
1. Space n → 新建会话(createSessionCmd + sessionCreatedMsg)
2. Space c → 手动 compact(status 提示,待后端 RPC)
3. Space m → 切换 build/plan(RuntimeState.AgentMode)
4. Space f → 切换 Full Access(RuntimeState.FullAccess)
5. Space l → 日志查看预留(status 提示)
6. Normal Mode ? → 打开帮助浮层(原 ActionSearchBackward 重映射)
7. Ctrl+L → 日志查看预留(新增 ActionLogViewer)
8. /model 命令 + 模型选择器浮层(components/model_picker.go)
9. 危险操作确认弹窗(components/confirm.go)

P1 修复(体验增强):
10. Palette 鼠标点击执行选项(MouseButtonLeft + MouseActionPress)
11. Session Picker 鼠标点击选择会话
12. Palette 鼠标滚轮滚动列表
13. Session Picker 鼠标滚轮滚动列表
14. /mode /compact /clear 命令路由补齐(palette + slash)
15. /model slash 命令路由
16. Normal Mode : Ex 命令预留位

新增文件:
- components/model_picker.go — Telescope 风格模型选择器
- components/confirm.go — 危险操作确认弹窗

修改文件:
- app.go — Leader handler 补全、新组件注册、消息路由
- state/viewstate.go — 新增 AgentMode/FullAccess/ConfirmState
- keymap/keys.go — 新增 ActionLogViewer、NormalHelp ? 条目
- palette.go — 重构 Update 为 handleKey/handleMouse,鼠标支持
- session_picker.go — 同上重构,鼠标支持,删除走确认弹窗

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
核心修复:事件流导致浮层组件持有过期 state 指针
state.Reduce 每次返回新的 *ViewState,a.state 会被替换;而 bindComponents 此前
只重新绑定 4 个静态组件(status/stream/prompt/inspector),遗漏了 palette、
helpOverlay、sessionPicker、modelPicker、confirmOverlay 等浮层组件。结果浮层的
按键操作改在旧 state 上、当前 state 的 Overlay.Active 始终不变,表现为:方向键
移到 /mode 后回车,面板不关闭、高亮跳回第一项 /model。同一根因也影响会话/模型
选择器与确认弹窗。现在 bindComponents 绑定全部子组件,问题根除。

其余配套修复:
- 命令面板 matchedItems 改用「精确名 → 前缀 → 子串」三级匹配(替换模糊匹配),
  避免 "mode" 因评分排序命中 /model;并将空格键改为「选中」,与会话选择器一致。
- 确认弹窗:确认/取消时调用 closeOverlay 重置 Overlay.Active,修复回车不关闭。
- 会话切换:sessionSwitchedMsg 重载历史后追加 "Switched to session" 状态提示。
- 多行消息:renderMessage 续行缩进对齐到首行标签宽度,修复流内正文错位。
- 输入提示符 › 改为 ASCII '>',消除歧义宽度在 CJK 终端导致的列错位。
- 换行键:新增 Ctrl+J(Shift+Enter 在 bubbletea v1.3.10 中与回车不可区分),
  并更新 keymap 帮助文案为 Alt+Enter / Ctrl+J。

测试:新增 repro_test.go(含模拟事件流复现过期指针的回归测试),并为 prompt、
stream 多行渲染与浮层交互补充回归测试。
@pionxe pionxe added the enhancement New feature or request label Jun 16, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review findings:

  • Permission and ask-user responses currently do not preserve the backend request identifiers, so real Gateway resolution cannot target the pending request.
  • Session deletion is exposed in the UI but only emits a local deletion event; it does not call the Gateway/backend.

Comment thread internal/tuiv2/app.go
Comment thread internal/tuiv2/app.go
@pionxe

pionxe commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

Review findings:

  • Permission and ask-user responses currently do not preserve the backend request identifiers, so real Gateway resolution cannot target the pending request.
  • Session deletion is exposed in the UI but only emits a local deletion event; it does not call the Gateway/backend.

当前只是使用假数据实现前端界面效果,并没有真实连通网关,并且现阶段也不打算这么做。

  针对 codecov 80% 门槛补齐 tuiv2 各包测试:
  - app_handlers_test.go:App 消息处理、Ctrl+C、命令分发、Cmd 工厂、模式键、鼠标、View
  - components: picker_test/coverage_test/prompt_keys_test — 直接覆盖 palette/model/session
    组件级函数、confirm/help/inspector/status_bar/stream 与 prompt 编辑/模式
  - theme/coverage_test.go:样式工厂与符号/颜色分支
  - gateway/real_test.go:RealClient 占位方法
@wynxing wynxing merged commit 36d1eba into 1024XEngineer:main Jun 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 9: Command Prompt 组件

2 participants