---
url: /wave-agent/specs/ui/transcript-find.md
description: >-
  会话内容区的就地查找条：Ctrl/Cmd+F 打开、在已加载的消息数据层匹配（虚拟列表下不依赖
  DOM）、命中高亮与计数、上一个/下一个、大小写/整词/正则、流式追加不打断当前命中；三端（desktop / VSCE / JetBrains）共用
---

# 功能规格说明：会话内查找

> 本规格描述「在一条已打开的会话里找回某句话」的就地查找能力。作用域是**当前会话已加载的内容**；跨会话搜正文见 [desktop-sessions.md](../desktop/desktop-sessions.md)「侧栏会话内容搜索」。文件面板里的查找条是同一形态的独立实现（见 [desktop-file-panel.md](../desktop/desktop-file-panel.md)「文件面板内查找与跳行」），两者同屏时按焦点归属决定谁响应快捷键。

## 用户场景与测试

### 用户故事：会话内查找（优先级：P2）

作为用户，我希望在一条长会话里按 `Ctrl/Cmd+F` 就地搜索并逐条跳转命中，以便在几百条消息里定位「那句话到底在第几轮说的」，不必用肉眼往回滚。

**为什么是这个优先级**：会话会越跑越长（工具调用、后台任务输出都在往里堆），而目前只有「往上滚」这一条找法。对齐 Claude 桌面端的 find-in-page：它同样**不走 Electron 原生 `webContents.findInPage`**，而是优先让页面在自己的数据树里查（原生查找只作兜底、5s 超时）——因为虚拟列表未渲染的行原生查找根本搜不到。形态与文件面板已有的查找条一致（大小写/整词/正则 + 计数 + 上一个/下一个），用户不需要学第二套控件。

**独立测试**：打开一条含几十条消息（含工具调用与输出）的会话，按 `Ctrl/Cmd+F` 验证查找条打开且焦点进输入框；输入一段只出现在早期消息里的文字，验证计数、高亮与自动滚动定位；按 Enter/Shift+Enter 在命中间移动并验证到末尾循环；切换大小写/整词/正则验证结果随之变化、输入非法正则验证可读提示；输入一个不存在的词验证「无匹配结果」且查找条不关闭；按 Esc 验证查找条关闭、高亮清除；在流式生成中打开查找并验证新内容到达时当前命中不被重置；把命中定位到虚拟列表尚未渲染的远端区段，验证能滚动过去并渲染出来。

**验收场景**：

1. **假设**会话内容区处于聚焦状态且消息列表已加载，**当**用户按 `Ctrl/Cmd+F`（macOS 为 `Cmd+F`），**则**必须就地打开查找条并把焦点放进输入框——不打开新面板、不打开对话框、不改变会话的滚动位置；查找条以浮层形式贴在内容区右上角，不挤压消息列表的可用宽度。
2. **假设**焦点在会话各处（输入框、消息行、工具行），**当**用户按 `Ctrl/Cmd+F`，**则**同上打开会话内查找；**例外**：焦点在文件面板内时该快捷键归文件面板的查找条（不打开会话内查找），两个查找条同一时刻至多开一个。
3. **假设**查找条已打开且用户输入关键字，**当**停止输入（无需回车，无「搜索」按钮），**则**必须显示匹配计数（`当前第几个 / 共几个`）并高亮全部命中，当前命中滚动到可视区并居中；匹配范围为会话**已加载的全部消息内容**：用户消息文本、助手消息文本（渲染后的纯文本，不是 markdown 源）、思考块正文、工具调用的名称与参数摘要、工具输出正文、计划与待确认卡片的文本。Diff 视图、文件面板、Artifact 卡片等独立面板的内容不参与会话内查找（各自有自己的查找入口）。
4. **假设**会话使用虚拟列表渲染（当前实现），**当**命中位于尚未渲染的区段，**则**仍必须能命中、能逐条跳过去并把目标行渲染出来——匹配**必须取自消息数据（状态里的消息数组）**，不得只搜 DOM 中已挂载的节点；这条是本故事的核心判据，决定了实现不能退化成 `webContents.findInPage`。
5. **假设**查找条已打开且有命中，**当**用户按 `Enter` 或点「下一个」，**则**必须在命中间前进；`Shift+Enter` 或「上一个」后退；到最后一个再前进必须回绕到第一个（反之亦然）。
6. **假设**查找条已打开，**当**用户切换「区分大小写」「整词匹配」「正则表达式」，**则**结果、计数与高亮必须即时更新，并重新从第一个命中开始；正则语法非法时必须给出可读提示（不静默吞掉），且不匹配任何内容、不清空已输入的关键字。
7. **假设**查找条已打开且关键字无匹配，**当**查看查找条，**则**必须显示「无匹配结果」且计数为 `0`，查找条保持打开（不得自动关闭），消息列表滚动位置不变。
8. **假设**查找条已打开，**当**用户按 `Esc` 或点关闭按钮，**则**必须关闭查找条、清除全部命中高亮、把焦点交回消息列表，滚动位置停留在最后一个当前命中处（不得弹回顶部或弹到底部）。
9. **假设**查找条已打开且当前有命中，**当**会话正在流式生成、新消息或工具输出追加进来，**则**当前命中索引与滚动位置必须保持不变（不因列表增长而跳动）；新内容里的命中必须计入总数，计数随之更新。
10. **假设**查找条已打开，**当**用户切换会话（或该会话被关闭 / 该分屏换了会话），**则**查找条必须关闭并清空关键字与高亮——会话切换后残留上一会话的高亮属于错误行为。
11. **假设**查找条已打开，**当**用户用键盘在查找条内操作（Tab 在关键字输入框 / 三个开关 / 上一个 / 下一个 / 关闭按钮之间移动，Enter 与 Shift+Enter 的语义同场景 5），**则**控件必须可按 Tab 逐个到达、有可见焦点指示；查找条内的按键不得冒泡触发行级快捷键（裸键、方向键导航）或全局快捷键。
12. **假设**命中落在一段 markdown 渲染结果里（加粗、行内代码、列表项、标题），**当**高亮渲染，**则**必须在渲染后的文本上做命中标记且**不破坏原有 markdown 结构**（不得把原始 markdown 源文本贴进渲染结果）；命中跨越多个渲染节点时（如加粗边界）按「同一段落内连续文本」匹配，允许在该段落内高亮相邻片段。

## 边界情况

* **超长单条消息**：单条消息正文可达数万字符，匹配必须线性扫描且不阻塞输入（输入防抖 + 分片计算）；不得因为一条长消息导致查找条输入卡顿。
* **正则回溯**：用户可输入任意正则，必须具备超时或步数上限保护，超限时按「正则过于复杂」提示并停止本次匹配，不得让 UI 卡死。
* **非 ASCII 大小写**：中文没有大小写，「区分大小写」对中文无影响；大小写折叠必须用完整 Unicode 折叠，不得只处理 `a-z`。
* **正在变化的行**：命中落在正在流式增长的最后一条消息上时，高亮必须随文本增长而延伸，不得留下半截高亮。
* **屏幕阅读器**：计数与「无匹配结果」状态必须通过 `aria-live` 播报，查找条本体为 `role="search"`。

## 非目标（明确排除）

* **替换**：会话内容是只读的，不提供替换输入框（文件面板的编辑态才需要替换）。
* **跨会话搜索**：本规格只作用于当前会话；跨会话搜正文见 [desktop-sessions.md](../desktop/desktop-sessions.md)「侧栏会话内容搜索」。
* **按行号跳转**：会话没有行号概念，不提供文件面板那样的跳行输入。
* **跳转到历史版本 / 编辑过的消息**：不引入任何会话内容改写能力。
