qwen-code/docs/design/tui-optimization/10-issue-oriented-flicker-plan.md

327 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 闪屏治理Issue 驱动的 4-PR 实施方案
> 目标:把闪屏优化从“围绕某个大 PR 拆补丁”改成“围绕真实用户问题逐类关闭 issue”同时把当前 8 条过细的 PR 计划压缩成 4 条主 PR并补齐下一步可以直接进入编码的执行边界。
> 校准时间点2026-04-24
> 使用方式:`09-pr-3013-gap-analysis.md` 只用于参考 `#3013` 里哪些 patch 被证明有价值真正的实施排期、PR 粒度和验证矩阵以本文档为准。
> 口径约束:如果 [00-overview.md](./00-overview.md)、[02-screen-flickering.md](./02-screen-flickering.md)、[08-execution-plan-and-test-matrix.md](./08-execution-plan-and-test-matrix.md) 中的技术阶段表或切片表与本文档的 PR 粒度看起来不同,以本文档为准;那些文档承担的是“技术演进顺序”和“文件/测试切片”视角。
## 1. 为什么从 8 条收成 4 条
8 条方案在“研究和拆因”层面是对的,但落到真实开发节奏上会有两个问题:
1. PR 太碎review 和回归成本偏高
2. 太多前后依赖会让排期看起来像 8 次独立发布,执行上反而拖慢
但我也不建议再继续压到 2-3 条。原因是下面两类问题必须保持隔离:
- **窄屏 / interactive shell 问题**:这是 shell serializer 和 viewport 语义问题,不能再混回主 UI PR
- **终端协议层问题**synchronized output / frame coalescing 需要按终端家族灰度,也不应和应用层修复绑死
因此,这一版把原来的 8 条收成 4 条主 PR作为执行层的平衡点。
## 2. 证据边界
本文档只使用四类证据:
1. **当前 qwen-code 源码**
2. **真实用户 issue**
3. **Gemini CLI / Claude Code 的可借鉴实现**
4. **`#3013` 中已被证明有价值的 patch**
其中第 4 类只做交叉印证,不作为拆 PR 的主轴。按 2026-04-24 复核,`#3013` 仍是 `OPEN + CHANGES_REQUESTED`,最近更新时间仍为 2026-04-22T11:28:48Z。
## 3. 4 条主 PR 总览
| 主 PR | 关闭的问题类 | 覆盖旧拆分 | 代表 issue | 主要范围 | 明确不带 |
| --- | --- | --- | --- | --- | --- |
| `PR-1` | 主屏闪烁基础修复 | `PR-Prep` + `PR-A1` + `PR-B1` 的安全子集 | `#1184` `#1491` `#3007` `#938` `#1861` `#2924`,以及 `#2748` 的 flicker 子问题 | counters、回归 harness、content/thought throttle、已清屏路径重复 clear 削减 | 不带 narrow-shell、不带 synchronized output、不带大输出详情重构 |
| `PR-2` | 大输出与详情展开稳定性 | `PR-D1` + `PR-E1` | `#1479` `#2424` `#2624`,以及 `#1491` `#1861` `#2924` 的展开子问题 | pre-slicing、stable height、bounded detail panel、update cadence decoupling | 不带 synchronized output、不带 shell serializer、不带 `refreshStatic()` 主链改造、不带 core budgeting |
| `PR-3` | 窄屏 / interactive shell 专项 | `PR-C1` | `#2912` `#2972` `#1591` `#1778` | shell serializer、live viewport vs transcript、interactive prompt 回归 | 不带 tool budgeting、不带 detail panel、不带终端协议层优化 |
| `PR-4` | 终端协议层残余闪烁收尾 | `PR-A2` | `#3144`,以及主屏闪烁在特定终端中的残余问题 | synchronized output、frame write 合并、runtime probe、allowlist | 不带主 UI 语义改造、不带 shell serializer、不带大输出重构 |
**非 flicker 主线 follow-up**
- `Follow-up-F1`:通用 tool budgeting 与 summary/detail 语义,主要对应 `#2818` `#1008` `#355`
- `Follow-up-F2`markdown-heavy 大输出的 parser/block 级降峰,属于渲染层 follow-up不纳入当前 4 条 flicker 主 PR
**PR1-4 之后的彻底闭环口径**
当前 4 条 PR 是必要基础,但它们不应被描述为“已经彻底关闭所有 TUI 闪烁 / 重复输出 issue”。`refreshStatic()` 替换型整屏 clear、bounded detail / virtual scroll、JetBrains / Windows / cmux 终端矩阵、以及 Claude 式 renderer ownership 的取舍,统一在 [15-complete-tui-flicker-closure-plan.md](./15-complete-tui-flicker-closure-plan.md) 中继续推进。后续不建议再拆成过多小 PR而应按 `Closure-A``Closure-B``Closure-C` 三个闭环包组织。
## 4. 推荐顺序
推荐按下面顺序推进:
1. `PR-1`
2. `PR-2`
3. `PR-3`
4. `PR-4`
排序理由:
- `PR-1` 先解决主路径上的“普通流式闪烁 + 整屏 clear”
- `PR-2` 再解决“大输出 / 展开详情导致的布局风暴和不可读”
- `PR-3` 继续单独收敛最复杂的窄屏 shell 问题
- `PR-4` 最后上终端协议层,是为了避免应用层问题没收敛前就引入终端家族灰度复杂度
## 5. `PR-1`:主屏闪烁基础修复
### 5.1 关闭的问题类
主屏路径上最常见的两类闪烁:
1. 普通 assistant 流式输出时的高频抖动
2. `refreshStatic()` 导致的整屏 clear + redraw
**Issue 边界**
- `#2748` 在这条 PR 中只作为 startup/view-switch flicker 样本,不代表这条 PR 会关闭其 slow startup 或 verbose output 全部诉求
### 5.2 合并后的范围
- 输出层 counters 与固定回归场景
- `useGeminiStream.ts` 的 content/thought throttle
- 强制 flush 语义
- `refreshStatic()` 的安全子集拆分:
- 已清屏路径使用 `remountStaticHistory()`
- 替换旧 static output 的路径继续保留 clear等待后续 renderer 策略
- slash clear / `/clear` 重复清屏削减
### 5.3 为什么这几块适合并在一起
它们都属于**主屏主路径的基础修复**
- 验证场景高度重合
- 同一组 counters 能同时衡量收益
- 都不依赖 shell serializer 或终端协议层 rollout
### 5.4 明确不带
- synchronized output
- narrow-shell / interactive shell 修复
- 大工具输出 pre-slicing
- stable height / bounded detail panel
### 5.5 固定验证场景
1. 长 assistant 回答(至少 500 token
2. thought + content 混合流
3. 冷启动(无 MCP
4. 冷启动(含 1 个慢 MCP server
5. `/settings` 上下切换
6. compact mode merge
7. active view switch
8. 终端宽高 resize
9. `/clear`
### 5.6 Done 定义
- `stdout.write` 频率显著下降
- `clear_terminal_count` 明显下降
- 结束 / cancel 不丢尾部内容
- 非致命布局变化不再默认清屏
## 6. `PR-2`:大输出与详情展开稳定性
### 6.1 关闭的问题类
这一条主 PR 负责解决“只要输出很大、或者一展开详情,界面就开始抖和变得不可读”的 UI 问题簇,包括:
1. 大工具输出导致的全量 layout
2. 工具 / 子 agent 展开导致的高度暴涨
3. detail surface 缺乏边界,导致主流式区域和详情区域互相拖累
### 6.2 合并后的范围
- plain text / ANSI pre-render slicing
- `MaxSizedBox` 降级成 safety net
- `useStableHeight`
- bounded detail panel
- assistant / tool progress / subagent progress 的刷新节奏解耦
### 6.3 为什么这几块适合并在一起
从用户视角,它们其实是同一个问题类:
- “大结果一出来就闪”
- “展开详情就抖”
- “结果太长时既难读又浪费上下文”
这些问题共用同一组回归样例,也都围绕 tool/subagent detail surface。
### 6.4 明确不带
- synchronized output
- shell serializer 改造
- `refreshStatic()` 主链改造
- 全量虚拟滚动
- scheduler 层统一 budgeting
- `llmContent` / 模型可见预算语义变更
- 以 markdown 降级替代真正的 parser/block 降峰
**Issue 边界**
- `#2818` `#1008` `#355` 不在这条 flicker 主 PR 的 closure 范围内,它们属于 `Follow-up-F1`
- markdown-heavy 大输出不应被这条 PR 宣称“已彻底解决”;它只需要保证不因本 PR 退化,并为后续 parser/block 降峰留下接口
### 6.5 固定验证场景
1. `npm install`
2. `git log --oneline`
3. 5000 行纯文本
4. ANSI 彩色输出
5. `ctrl+e`
6. `ctrl+f`
7. subagent 执行中展开
8. markdown-heavy 工具结果(仅验证“不退化”和“不会错误 claim 已解决”)
### 6.6 Done 定义
- 大工具输出不再每次 layout 全量内容
- 展开详情不再造成明显闪屏
- hidden lines 统计可解释
- 不引入 `llmContent` 语义变化
## 7. `PR-3`:窄屏 / interactive shell 专项
### 7.1 关闭的问题类
窄终端、多 pane tmux、interactive shell 场景下的:
- 重复打印
- 顶部 / 底部来回跳
- 无限滚动
### 7.2 范围
- `shellExecutionService.ts`
- `terminalSerializer.ts`
- live viewport / transcript archival 语义拆分
- interactive / integration 回归
### 7.3 为什么必须单独保留
这不是普通的“主屏闪烁”问题,而是 shell viewport 序列化和滚动语义问题。继续把它混进主 UI PR会同时拖慢验证和误伤其他路径。
### 7.4 明确不带
- synchronized output
- tool budgeting
- bounded detail panel
- `refreshStatic()` 语义改造
### 7.5 固定验证场景
1. 40 列以下窄终端
2. tmux 多 pane
3. 宽度缩小后继续输出
4. `git commit`
5. interactive shell prompt / pager
6. `showColor=true` 的彩色 shell 输出
7. `showColor=true` + tmux / 窄终端 组合路径
### 7.6 Done 定义
- 不再重复刷旧 viewport
- 顶 / 底往返滚动停止
- 文档与实现中不再把 `#1778` 的 one-line fix 当作现状根因
## 8. `PR-4`:终端协议层残余闪烁收尾
### 8.1 关闭的问题类
在主 UI 路径已经收敛后,特定终端中仍残留的帧撕裂和可见中间帧问题。
**Issue 边界**
- `#2903` 在这条 PR 中是必须显式验证的 JetBrains 环境样本,不是默认的 closure target
- 只有当 JetBrains 被明确纳入 support matrix / allowlist / probe 成功路径时,才应把它提升为“修复目标”
### 8.2 范围
- synchronized output wrapper
- frame 内 write 合并
- runtime probe / allowlist
- fallback / rollback
### 8.3 为什么必须最后做
这条 PR 的收益很大,但风险也最高:
- 依赖终端家族差异
- 涉及 stdout monkeypatch 和 callback / Buffer 语义
- 如果应用层问题没先收敛,协议层 patch 的收益很难验证
### 8.4 明确不带
- 主屏语义改造
- shell serializer
- 大输出和 detail panel 重构
### 8.5 固定验证场景
1. WezTerm
2. kitty
3. JetBrains 终端
4. tmux / SSH
5. Terminal.app 或未命中 allowlist 的终端
### 8.6 Done 定义
- 已支持终端的可见帧撕裂下降
- 未纳入 allowlist 的终端不出现退化
- Buffer / callback 语义不变
- JetBrains 若未进入 allowlist/probe 成功路径,则至少验证“不退化”,不宣称已修复
## 9. 为什么不再继续往下合
压到 4 条以后,我不建议再继续合并:
1. **不要把 `PR-3` 合进其他 PR**
窄屏 / shell 问题的验证方法完全不同。
2. **不要把 `PR-4` 合进其他 PR**
终端协议层需要单独灰度和单独回滚。
3. **不要把 `PR-1` 和 `PR-2` 合并**
合起来就会重新变成一个“主屏语义 + 大输出表面 + detail panel + budgeting”的超大 PRreview 会再次失焦。
## 10. 统一 PR 模板
每条主 PR 都建议强制使用同一模板:
1. **Problem Class**
- 本 PR 关闭哪一类用户问题
2. **Linked Issues**
- 只链接与这一类问题直接相关的 issue
3. **Scope**
- 明确文件和模块
4. **Non-goals**
- 明确故意不带什么
5. **Reference**
- 若借鉴 `#3013` / Gemini CLI / Claude Code写清楚借鉴点
6. **Validation**
- 固定复现场景
7. **Rollback**
- 开关或失败信号
## 11. 最终建议
如果现在就要进入真实实施,我建议按下面的 4 条主 PR 推进:
1. `PR-1`:主屏闪烁基础修复
2. `PR-2`:大输出与详情展开稳定性
3. `PR-3`:窄屏 / interactive shell 专项
4. `PR-4`:终端协议层残余闪烁收尾
在这 4 条之后,再进入两个 follow-up
5. `Follow-up-F1`:通用 tool budgeting
6. `Follow-up-F2`markdown-heavy 输出降峰与 parser/block 层优化
这样数量比 8 条明显更可执行,但还没有退回到“一个巨大 PR 试图一口吃掉所有闪屏问题”的老路。
如果下一步准备直接进入实现,请按顺序使用:
1. [11-pr1-implementation-checklist.md](./11-pr1-implementation-checklist.md)
2. [12-pr2-implementation-checklist.md](./12-pr2-implementation-checklist.md)
3. [13-pr3-implementation-checklist.md](./13-pr3-implementation-checklist.md)
4. [14-pr4-implementation-checklist.md](./14-pr4-implementation-checklist.md)