---
url: /wave-agent/specs/desktop/desktop-preview.md
description: CodeWave IDE 桌面端独有原型预览闭环：localhost 原型预览、本地 HTML 文件预览、元素评论、全屏与跨会话保活
---

# 功能规格说明：桌面端原型预览

## 用户场景与测试

### 用户故事：localhost 原型预览（优先级：P2）

作为用户，当 agent 为我启动了本地开发服务器（如 Vite 原型）后，我希望点击消息列表中的 localhost 链接时在应用右侧打开预览面板，而不是跳转到外部浏览器，以便我边对话边看原型。

**为什么是这个优先级**：预览面板是「指哪改哪」闭环的载体，没有它元素评论无从谈起；但应用基础对话能力不依赖它。

**独立测试**：让 agent 启动一个 localhost dev server，点击消息中的 localhost 链接，验证右侧预览面板加载页面；点击非 localhost 链接验证仍用外部浏览器打开。

**验收场景**：

1. **假设**消息列表中渲染了 localhost 链接（`localhost`、`127.0.0.1`、`[::1]`，任意端口），**当**用户点击，**则**必须在右侧预览面板打开并加载该 URL——新增/激活 preview tab 与隧道复用的实例语义见 [desktop-right-panel.md](./desktop-right-panel.md)「右侧面板 · 一级 Tab 栏与实例语义」场景 1-2，此处不重复。
2. **假设**用户点击非 localhost 的 http(s) 链接，**则**必须用系统默认浏览器打开，行为与现状一致。
3. **假设**预览面板已打开，**当**用户点击对应 tab 的关闭按钮，**则**关闭该 preview tab 即关闭对应预览；关闭最后一个 tab 时右侧面板随之一并收起（不显示空态，见 [desktop-right-panel.md](./desktop-right-panel.md)「右侧面板 · 一级 Tab 栏与实例语义」场景 10）；再次点击 localhost 链接可重新打开。
4. **假设**预览中的 dev server 支持热更新，**当** agent 修改源文件，**则**预览必须通过 dev server 自身的热更新机制自动反映改动；面板同时必须提供手动刷新按钮，且刷新必须绕过 HTTP 缓存（强制重新加载），确保 dev server 更新后的内容立即可见、不被过期缓存遮挡。
5. **假设**预览面板已打开，**当**用户点击「在浏览器打开」，**则**必须用系统默认浏览器打开当前预览 URL。
6. **假设**目标 localhost 服务不可达（未启动/已停止/端口错误），**当**预览加载失败，**则**面板必须显示可操作的错误提示（如「无法连接」+ 重试），而不是白屏。
7. **假设**预览页面自身的内容宽度超出面板可视宽度（如桌面布局的管理后台页面在窄面板中），**当**页面加载完成或面板宽度变化，**则**预览必须自动等比缩小页面内容以完整适配面板宽度（只缩小、不放大），确保超出部分可见且可点击/可选择，而不是被裁剪后无法操作；页面自身已完整适配面板宽度时不得放大页面内容（对齐 Claude Desktop 浏览器面板的缩放适配行为）。
8. **假设**页面在窄面板下已按宽度缩小适配（zoom < 100%），**当**预览面板进入全屏（面板被 CSS 撑宽、对话列隐藏）或该 preview tab 从 `display:none` 恢复可见（tab 切换/折叠后再展开，重挂载触发 resize），**则**必须按面板当时的实际宽度重新计算缩放比例——页面能放下时恢复到 100%，仍放不下时放大到恰好适配当前全屏宽度；不得沿用进入全屏/恢复前按窄面板算出的缩小比例（否则页面只占面板左上角一小块、其余留白）。
9. **假设**某 localhost 预览已在会话 A 打开并加载（页面内可能已有状态：地址、表单/展开项、滚动位置），**当**用户切到另一会话再切回 A（或该面板随分屏移动重建），**则**预览必须保活、不重新加载，页面内状态与地址原样保留（保活语义、全局 4 条上限与销毁时机见 [desktop-right-panel.md](./desktop-right-panel.md)「右侧面板 · preview guest 跨会话保活」）；仅当该 guest 已被销毁（关闭 tab／删除会话／关闭分屏／超出保活上限被淘汰）时，才按会话记忆的最后地址重新加载（沿用场景 1 的实例语义与 [desktop-right-panel.md](./desktop-right-panel.md) 场景 14）。

***

### 用户故事：本地 HTML 文件预览（优先级：P2）

作为桌面端用户，当 agent 为我生成或修改了一个本地 HTML 文件后，我希望点击消息中的该文件路径时在右侧预览面板看到**渲染后的页面**（而非源代码），以便直接核对 agent 产出的页面效果；页面打开不了时回退到文件面板查看源码，源码与渲染两条路都可达。

**为什么是这个优先级**：agent 产出单文件 HTML（数据报表、原型页、可视化）是高频场景，现状点击 `.html` 路径只能看到源代码高亮，核对效果必须跳出到外部浏览器；预览面板与 tab 基础设施均已存在，本故事只是把「本地文件」接入同一条预览管线（对齐 Claude 桌面端 Code 标签页：`.html/.htm/.svg/.pdf/视频` 点击走预览，失败回落文件面板；本故事范围仅 `.html`/`.htm`）。

**独立测试**：让 agent 在本地会话生成一个引用同目录 `./style.css` 与 `./app.js` 的 `report.html`，点击消息中的路径，验证预览面板新增 preview tab 且页面带样式/脚本完整渲染；再次点击同一路径验证激活既有 tab 不新增；点击另一 `.html` 验证新增 tab；让 agent edit 该文件并成功，验证预览自动重载为最新内容；删除文件后再点击，验证回落文件面板并显示其既有错误态；远程会话重复上述路径点击，验证主体内容渲染。插件端（VS Code/JetBrains）中点击同一路径，验证仍由 IDE 编辑器打开（行为不变）。

**验收场景**：

1. **假设**本地会话消息中出现 `.html`/`.htm` 文件路径（read/write/edit 工具头或行内路径链接），**当**用户点击，**则**在右侧预览面板打开并渲染该文件（新增 preview tab，实例语义见 [desktop-right-panel.md](./desktop-right-panel.md)「右侧面板 · 一级 Tab 栏与实例语义」场景 1），且页面内的相对资源（`./style.css`、`./app.js`、`./img/` 等同目录及子目录引用）必须正常加载——宿主以**文件所在目录为服务根**重托管该文件（具体机制由技术方案定），不得因相对路径解析不到而裸奔；同一文件被多处引用（`<link>`/`<script>`/`<img>`）经同一服务根命中。
2. **假设**同一会话中已打开某 `.html` 的 preview tab，**当**用户再次点击消息中**同一文件**的路径（按规范绝对路径判等，含 `..` 归并后等价的路径），**则**激活该既有 tab 并重新加载最新内容，不新增 tab；点击**不同** `.html` 文件则新增 tab（preview 多实例语义不变，本条为其按「文件路径」判等的例外条款，对应远端 localhost 链接的隧道复用例外见 [desktop-right-panel.md](./desktop-right-panel.md) 场景 2）。
3. **假设**远程会话（host 非 local）消息中出现 `.html`/`.htm` 文件路径，**当**用户点击，**则**同样在预览面板渲染该文件（宿主读取远端文件内容后重托管）；首版为**内容级**重托管——页面对外部相对资源的引用（同目录 css/js/图片）允许缺失，页面按浏览器原生降级渲染（无样式/裂图不算错误、不弹提示），页面主体（HTML 结构与内联样式/脚本）必须可见。
4. **假设**被点击的 `.html` 文件不存在、不可读或超出可预览大小上限，**当**用户点击，**则**回退到文件面板并按其既有失败语义展示（[desktop-file-panel.md](./desktop-file-panel.md) 场景 16：面板内错误提示，不弹「打开文件失败」系统消息）；宿主重托管能力不可用时同样回退文件面板展示源码，不得白屏或无响应。
5. **假设**某 `.html` 的 preview tab 处于激活状态，**当**agent 对该文件执行 write/edit 工具并成功完成，**则**该 preview tab 自动重新加载最新内容（与文件面板自动刷新的触发条件一致，见 [desktop-file-panel.md](./desktop-file-panel.md)「文件面板自动刷新」）；手动刷新按钮同样可用且绕过缓存（沿用本规格 localhost 预览场景 4 的语义）；agent 修改的是**其他**文件（如被引用的 `./style.css`）时不自动重载，由用户手动刷新。
6. **假设**本地 `.html` 的 preview tab 打开中，**当**用户点击「在浏览器打开」，**则**用系统默认浏览器打开该文件（渲染语义与面板一致：同目录相对资源可用），沿用本规格场景 5 的按钮语义。
7. **假设**preview tab 打开的是本地 `.html` 文件，**当**查看 tab 标签，**则**沿用 [desktop-right-panel.md](./desktop-right-panel.md) 场景 3 的标题语义（页面 `<title>` → 文件名回退），不显示完整路径。
8. **假设**本地 `.html` 重托管服务已建立，**当**服务收到对其服务根之外路径的请求（目录穿越，如 `../../settings.json`）或对非该会话已预览文件的请求，**则**必须拒绝（403/404），服务仅对本机回环地址可达；tab 关闭且无其他 tab 复用该服务时，服务必须随之销毁（不残留进程/端口）。
9. **假设**用户在 VS Code/JetBrains 插件中点击同样的 `.html` 路径，**当**点击发生，**则**行为与现状完全一致（IDE 编辑器打开），本故事全部能力为桌面端独有。

***

### 用户故事：预览页元素评论（优先级：P2）

作为用户，我希望在预览面板里悬停高亮某个元素、点击它并写下评论，评论连同元素上下文一起追加到消息输入框，以便我连续评论多个元素后统一发给 agent，agent 精确理解我指的是哪里并据此修改原型。

**为什么是这个优先级**：这是「指哪改哪」闭环的核心交互，是本特性区别于「单纯开个浏览器」的价值所在。

**独立测试**：打开预览 → 开启元素拾取 → 悬停验证高亮 → 点击元素 → 输入评论并添加 → 验证评论连同元素上下文追加到消息输入框（不直接发送）→ 再拾取另一元素添加第二条评论 → 验证两条评论都在输入框中，发送后聊天中出现一条含全部评论的用户消息。

**验收场景**：

1. **假设**预览面板已加载页面，**当**用户开启「元素拾取」模式，**则**鼠标悬停的元素必须显示高亮轮廓，且页面自身交互（点击、链接跳转、表单提交）被拾取拦截。
2. **假设**拾取模式已开启，**当**用户点击某元素，**则**必须显示跟随该元素的浮层评论卡片：评论输入框底部左侧显示元素标签名、右侧为无文字的圆形添加图标按钮；被选元素保持定格高亮；用户可回车或点击添加图标提交评论，可通过点击卡片外空白处取消，取消后回到悬停拾取状态、可改选其他元素。
3. **假设**用户提交评论，**则**不得直接发送给 agent，必须将评论追加到消息列表下方的输入框中，追加内容包含：页面 URL、元素 CSS 选择器、元素摘要、评论文本；输入框已有内容时必须保留并在其后追加；由用户在输入框中统一编辑后手动发送，发送后的消息呈现与普通用户消息一致。
4. **假设**拾取模式已开启，**当**用户再次点击拾取开关，**则**必须退出拾取模式、恢复页面正常交互；提交评论后必须保持拾取模式、回到悬停拾取状态，用户可连续拾取多个元素逐条添加评论（支持连续评论）。
5. **假设**页面发生导航或整体刷新，**则**拾取模式必须重置为关闭状态，防止拾取脚本随页面重建失效后界面状态不一致。
6. **假设**拾取模式已开启，**当**用户点击一个不可交互的元素（`disabled` 表单控件、`fieldset[disabled]` 内的控件、`aria-disabled` 行），**则**必须与可交互元素行为一致：弹出浮层评论卡片并定格高亮该元素；命中的是禁用控件内部的子元素时以该子元素为评论目标（与可交互元素的命中层级一致，也与悬停高亮一致）。`pointer-events: none` 的元素不参与命中，命中的是其后方元素（同样与悬停高亮一致）。

***

### 用户故事：预览面板全屏（优先级：P2）

作为桌面端用户，我希望预览面板可以全屏（占满内容区），以便专注查看网页/应用预览效果。

**为什么是这个优先级**：原型 `InspectorPanel.vue` 标签栏提供「全屏预览」按钮（toggleFullscreen），设计师 zip 对比（2026-08-29）确认 wave 缺失；全屏形态在原型 CSS 中定义为面板占满内容区、对话区隐藏（`.inspector-fullscreen`）。全屏按钮作用于整个槽位（tab 化模型下位于 tab 栏右端），槽位整体收起由头部按钮承载（见 [desktop-right-panel.md](./desktop-right-panel.md)「右侧面板 · 展开/折叠、空间守卫与欢迎页共存」）。全屏里还要放一个浮动 AI 对话框：全屏状态下对页面做元素标注后，对话区不可见，用户无从判断「标注有没有发出去、agent 有没有在改」（需求八，2026-09-16），原型「需求八画布 · 全屏预览右下角的悬浮对话框」给出形态。

**独立测试**：打开预览面板，验证 tab 栏右端「全屏预览」按钮点击后整个槽位占满内容区（对话区隐藏）、按钮图标切换，再次点击或按 Esc 还原布局；输入框里留下未发送的内容，进出全屏后内容与滚动位置不变、焦点回到输入框；全屏下底部出现浮动对话框，在预览里拾取元素提交评论后评论出现在该对话框里且仍留在全屏，点发送后对话框转为「正在工作」，本轮结束后恢复空白输入框。

**验收场景**：

1. **假设**预览 tab 处于激活状态且槽位 tab 栏存在，**当**用户点击 tab 栏右端「全屏预览」按钮，**则**整个槽位占满内容区（对话区与其它 tab 隐藏），按钮图标切换为「退出全屏」。
2. **假设**槽位处于全屏状态，**当**用户再次点击「退出全屏」按钮或按 Esc，**则**退出全屏并恢复原布局（对话区与其它 tab 按全屏前的 tab 集合与宽度还原）。
3. **假设**槽位含多个 tab（多实例 preview 或混合类型），**当**槽位全屏，**则**仅当前激活 tab 的面板显示；还原后各 tab 宽度与激活态保持全屏前状态。
4. **假设**槽位全屏期间会话仍在运行，**当**查看对话状态，**则**消息流式更新不受影响（对话区隐藏但会话照常进行，退出全屏后可见最新消息）。
5. **假设**槽位处于全屏状态（此时对话区已隐藏、无可视退出入口），**当**用户关闭最后一个 tab（面板随之一并收起）或在侧边栏切换到另一会话，**则**退出全屏并恢复对话区——任何关闭/切换路径都不得让全屏状态残留（残留会同时隐藏对话区与面板区，内容区黑屏且无任何 UI 可恢复，见 PM bug 3465519197988352）。
6. **假设**槽位处于全屏状态，**当**用户在面板里提交一条评论（预览元素拾取、diff/文件行评论），**则**保持在全屏，把评论追加进浮动对话框、对话框获得焦点——用户在全屏里就能看见刚加的标注，不必退出全屏确认；连续提交的多条评论按追加顺序累积，不覆盖对话框里已有的内容。评论不得因「对话区不在视野里」被静默丢弃。
7. **假设**对话输入框里有尚未发送的内容，**当**用户进入全屏再退出全屏，**则**输入内容必须原样保留（含刚敲进去、尚未上报宿主的最后一段），对话的滚动位置一并保持，退出后焦点回到输入框即可继续输入——全屏只能隐藏对话区、不得把对话区连同输入框一起卸载重建。
8. **假设**槽位处于全屏状态，**当**用户查看内容区，**则**底部必须有一个悬浮的 AI 对话框（水平居中、贴底约 24px、宽度约 420px，盖在预览之上），能力与对话输入框一致（输入、发送、斜杠指令、附件、模型切换、权限模式）；消息列表、任务清单与消息队列区不显示，预览不被对话框顶出可视区。
9. **假设**用户在浮动对话框里发送了一条消息，**当**这一轮尚未结束，**则**对话框位置显示「正在工作」加载态（脉冲点 + 文案），已发送的消息不在浮动框里重复显示；**当**本轮结束（生成完成或被中断），**则**恢复为空白输入框、焦点回到输入框；退出全屏后对话区照常显示完整消息流（含本轮新增的消息）。

### 边界情况

* **预览目标服务不可达**：预览加载失败时必须显示错误提示与重试入口，不得白屏或影响聊天区。
* **拾取脚本注入失败**（页面异常、加载中途开启拾取等）：必须提示「该页面暂不支持元素拾取」并保持面板可用，不得崩溃。
* **被评论元素随后被 agent 改动**：评论携带的是提交时刻的上下文快照；选择器失效不构成错误，后续评论需重新拾取。
* **HTML 文件含脚本错误或引用网络资源失败**：预览按浏览器原生行为渲染（控制台错误不影响面板），不得因页面自身 JS 报错而判定预览失败；页面 `<script>` 在预览中允许执行（与外部浏览器打开同语义），风险面由场景 9 的服务根隔离与本机回环限制兜底。
* **重托管与文件落盘的竞态**（agent streaming 写入中途点击路径）：读到什么渲染什么，文件随后写完由场景 5 的自动重载收敛到最终态，不对半成品文件做特殊提示。
* **元素拾取与本地文件预览的叠加**：首版不承诺本地文件预览 tab 支持元素拾取/评论（该能力面向 dev server 原型），叠加适配留待后续按需求排期。
* **预览页面跨会话保活**：切会话／分屏重排不销毁 preview guest（页面在后台活着、切回即原样显示），仅关闭 tab、删除会话、关闭分屏、超出全局 4 条上限被 LRU 淘汰或退出应用时销毁；保活期间不得出现重复注入（拾取脚本/样式）或评论串台——元素评论只来自当前可见的 preview，保活时自动退出拾取（完整验收见 [desktop-right-panel.md](./desktop-right-panel.md)「右侧面板 · preview guest 跨会话保活」）。

## 非目标（明确排除）

* **插件端的预览与元素评论**：预览面板与元素拾取评论为 desktop 独有，VSCE/JetBrains 不提供。
* **URL 到源码文件的精确定位**（如通过构建工具插件注入源码位置信息）：首版不做，依赖 agent 会话上下文与元素评论上下文定位。
* **拾取模式的键盘快捷键**（Esc 退出等）：首版不做，仅通过开关按钮管理拾取状态。
* **元素截图**：首版评论消息不附带元素截图，仅携带页面 URL、CSS 选择器与元素摘要等文本上下文。
* **`.html`/`.htm` 以外的扩展名路由到文件预览**：Claude 桌面端把 `.svg`/`.pdf`/视频也划入预览类；wave 首版仅 `.html`/`.htm`——`.svg` 维持文件面板图片内联现状（[desktop-file-panel.md](./desktop-file-panel.md) 场景 15/19），`.pdf`/视频维持现状（源码/外部打开）。
* **远程会话的目录级重托管**：首版远程会话为内容级重托管（场景 3），相对资源经宿主 SSH 桥解析的目录级能力不做；若体验不可接受再按本故事场景 1 的本地语义扩展。
* **预览到源码的直达入口**（预览工具栏「查看源码」按钮等）：不做；源码查看走消息路径点击的回退链（场景 4）或文件面板既有入口，对齐 Claude 桌面端（预览面板无跳源码按钮）。
* **preview tab 内的渲染/源码双视图切换**（对齐 Claude 桌面端 artifact 页的 Preview/Code 分段控件）：不做。
* **preview guest 跨应用重启保活**：不做——保活只发生在运行期间（运行期切会话不丢页面），重启应用后预览按既有「每次启动全新开始」规则清空。
* **保活页面的视觉占位（截图回落）**：不做——保活 guest 在不可见期间不参与绘制，切回即显示活页面（参考实现为停驻视图配截图占位，wave 无此需要）。
