Skip to content

功能规格说明:桌面端差异面板 ​

本规格描述右侧面板里的「差异」这个实例:看什么基准的改动、怎么切换口径、怎么导航与评论。面板槽位(tab 栏、开合、宽度)见 desktop-right-panel.md。

用户场景与测试 ​

用户故事:会话变更差异面板(优先级:P1) ​

作为桌面端用户,我希望在对话旁打开差异面板,默认实时查看本次会话相对仓库基线的全部改动(agent 已提交到会话分支的、工作区尚未提交的、以及尚未跟踪的新文件),也能按需切到「未提交改动」口径,以便不离开应用即可审查 agent 这一轮到底改了什么,并在批次改动已被合并时仍有出口。面板的数据源是会话的工作根:从会话打开时的目录起步,agent 用 EnterWorktree 切进工作树时随之切换、ExitWorktree 时切回,而 agent 在 shell 里临时 cd 不改变它——用户在面板里看的是「这条会话在哪个工作区里改了什么」,不是「agent 刚才 cd 到了哪里」。

为什么是这个优先级:审查改动是 agent 编程的核心回路,desktop 无编辑器内建 diff 视图,必须自备;且 worktree 会话中 agent 的产出常以 commit 形式落地——若面板只显示未提交改动,agent 一提交面板就会变成「无改动」而误导用户,变更基准语义是该面板可用性的前提(P1)。

独立测试:在 git 仓库会话中让 agent 修改若干文件,打开差异面板验证逐文件差异区块与折叠交互;让 agent 在 worktree 会话中产生 commit,验证面板仍显示这些改动(相对变更基准);手动改动工作区文件后点击刷新验证更新;非 git 目录下验证空状态提示;在无 origin、无 main/master 的仓库验证退化为 HEAD 基准;用 --rebase 把会话分支的提交合进默认分支后验证切到「未提交改动」为空、切回「全部改动」仍能看到本会话产出;制造一个未完成的 merge 验证面板不再把 incoming 改动当作用户改动列出;让 agent 在 shell 里 cd 到仓库内的子目录、仓库内的另一个工作树(.wave/worktrees/<name>)与容器内另一个 git 仓库,验证面板内容与统计均不变、且不回到加载占位。

验收场景:

  1. 假设用户在 git 仓库的会话中通过空态入口/「+」菜单/菜单栏「面板」打开差异面板,当面板打开,则必须展示变更基准到当前工作树的全部改动:变更基准 = 会话分支相对仓库默认分支的分叉点(git merge-base HEAD <默认分支>),因此 agent 已提交到会话分支的改动与尚未提交的工作区改动一并计入;未跟踪的新文件按纯新增计入。
  2. 假设面板解析变更基准,当查找默认分支,则按以下顺序取第一个可用者:外部注入的基线(WAVE_DIFF_BASE_REF 环境变量给定的 ref,供沙箱/托管容器场景使用)→ 远端默认分支(refs/remotes/origin/HEAD 指向的分支,如 main)→ 本地存在的 main → 本地存在的 master;候选全部不可用(注入值解析不到、没有默认分支可用、或分叉点计算失败/无共同祖先)时,变更基准退化为 HEAD——此时面板只显示未提交改动(等价于无基线能力时的既有行为),不得报错、不得展示空面板。
  3. 假设仓库尚无任何 commit(无 HEAD),当面板打开,则变更基准退化为已暂存内容(--cached 语义),面板展示已暂存与未跟踪的文件,不报错。
  4. 假设面板已打开,当查看面板工具栏,则必须以只读形式展示当前对比的两端 {变更基准} → 工作树(两端本身不可点击,基线不可改选;完整 sha 在 title 提示中给出,退化为 HEAD 时显示 HEAD),并展示改动文件数与总增删统计(N 个文件 / +A / −D);查看范围不是由这里切换,而由左侧栏的范围选择承载(见「差异面板查看范围」)。
  5. 假设面板打开且当前范围有改动,当查看内容,则必须以手风琴形式逐文件展示改动:每个文件一个可折叠区块,区块头部显示相对路径、状态标识(新增/修改/删除/重命名/未跟踪)与增删行统计,展开区显示该文件差异内容(含行级增删标识与词级高亮,见场景 9);默认展开第一个文件,点击头部折叠/展开;文件级导航改由文件树承载(见「差异面板文件树与导航」)。
  6. 假设面板已打开且可见,当该分屏会话的一轮生成结束(agent 可能改动了文件),则面板必须自动刷新为最新改动;面板同时必须提供手动刷新按钮。自动或手动刷新期间必须保留现有内容直至新数据到达后一次性替换,不得清空为加载占位导致闪烁;仅切换会话/工作目录时才重置为加载状态;刷新进行期间刷新按钮显示旋转动效。
  7. 假设会话工作根不是 git 仓库或 git 不可用,当面板打开,则必须显示「非 git 仓库」空状态提示,不得报错影响聊天区。
  8. 假设当前查看范围没有任何改动,当面板打开,则必须显示与该范围对应的空状态:范围是「全部改动」(及单条 commit)时显示「无改动」,范围是「未提交改动」时显示「无未提交改动」。
  9. 假设工作区有多个改动文件,当面板打开或用户点击某个文件头部,则差异数据仍一次性加载(一次请求取回全部文件路径与差异内容),但同一时刻只展开一个文件区块:点击某文件会自动收起当前展开的其他文件,点击已展开文件则收起它;DOM 仅保留该文件的差异内容与全部文件头部,刷新时保持当前展开的文件不变。
  10. 假设展开区显示某文件的差异内容,当同一处改动含删除行与新增行,则删除行与新增行按位置逐对配对做词级高亮:仅变化的词以词级高亮标出(删除词附删除线),未变化的部分只保留行级标识;无配对对象的行(纯新增、纯删除的整块行)整行以词级高亮标出;配对按 hunk 内连续块进行,遇到上下文行或下一个 hunk 头时结束当前配对块。
  11. 假设同一文件既在会话分支上被提交过、在工作区又有未提交改动,当面板展示该文件,则该文件只出现一次,展示「变更基准 → 工作树」的合并差异(已提交部分与未提交部分不拆成两条,统计为合并后的净增删)。
  12. 假设面板已打开,当用户在该分屏切换到另一条同样打开了差异面板的会话,则面板必须按新会话的工作根与变更基准重新拉取并展示最新改动;目标会话无 diff tab/无任何 tab 时,槽位随会话面板记忆切换(见「一级 Tab 栏与实例语义」场景 8 与「展开/折叠」会话切换语义),不把上一会话的 diff 内容带过去。
  13. 假设仓库正处在 merge / rebase / cherry-pick / revert 的中间状态(MERGE_HEAD / REBASE_HEAD / CHERRY_PICK_HEAD / REVERT_HEAD 之一存在),当面板打开或刷新,则必须视空并给出一句说明(如「正在 rebase,暂不展示差异」):此刻工作区里的改动不是用户有意做的(是待并入的外部改动),不得把它们当作用户改动列出来;该规则对所有查看范围一视同仁,用户手动刷新不改变结论(操作完成后自然恢复)。
  14. 假设该会话里 agent 通过 shell 执行过 cd(到仓库内的子目录、到会话工作根内的另一个 git 仓库或其工作树如 .wave/worktrees/<name>,或到被权限安全区重置回原地的目录),当面板打开、刷新或用户切回该会话,则面板的数据源与变更基准必须始终按会话工作根解析,cd 到哪都不改变看的是哪个仓库:不得改看 cd 后的目录、不得因这次 cd 把面板重置为加载占位、不得出现本会话改动「消失」或换成另一仓库改动的情况;同一会话里 cd 多次结论相同,与文件面板目录树的根、@ 文件搜索同口径。
  15. 假设agent 用 EnterWorktree 把会话切进一个工作树(此后又用 ExitWorktree 切回),当面板打开、刷新或用户切回该会话,则面板的数据源必须随会话工作根一起切换:切进工作树后展示该工作树相对基线的改动、切回后回到原目录的改动,不残留上一个工作区的文件列表;与 cd 不同,这是用户可预期的会话位置变化,切换时允许重置为加载占位(同场景 6 的「切换会话/工作目录」)。

用户故事:差异面板查看范围(优先级:P2) ​

作为桌面端用户,我希望在差异面板里切换查看范围——「全部改动」(变更基准 → 工作树)与「未提交改动」(HEAD → 工作树),以便在会话分支上的批次改动已被合进默认分支时,仍能切到一个干净的口径继续工作,而不是被一堆早已合并的差异堵住且无路可走。

为什么是这个优先级:--rebase 合并把提交改写成新 sha,会话分支那枚原提交不再是默认分支的祖先,分叉点基线的口径下这批已合并改动会一直显示;面板不提供出口就等于把这个口径强加给用户。claude.ai 的差异面板正是把口径交给用户(All changes / Uncommitted changes),两种口径共用同一份取数链路、只换基线,增量成本低(P2)。

独立测试:在会话分支产生 commit 后切到「未提交改动」验证面板为空、工具栏两端变为 HEAD → 工作树;再手动改一个工作区文件验证只显示该文件;切回「全部改动」验证恢复本会话全部产出;在无 commit 的仓库验证两个范围都可用且不报错。

验收场景:

  1. 假设面板已加载改动,当查看左侧栏,则必须提供查看范围选择(位于文件树下方、提交列表上方):两个单选选项「全部改动」「未提交改动」,默认选中「全部改动」且其语义与既有行为完全一致(变更基准 → 工作树)。
  2. 假设用户选择「未提交改动」,当选择生效,则面板改为只展示 HEAD → 工作树 的改动:本会话在分支上已提交的改动不再计入,已暂存与未暂存的工作区改动、以及尚未跟踪的新文件均按未提交改动计入;文件列表、文件数、总增删统计、文件树、工具栏两端一并切换。
  3. 假设用户已选择某条 commit 作为查看范围,当查看左侧栏,则两个范围选项均不处于选中态(选中态由该 commit 行体现),点击任一范围选项即离开该 commit 范围并回到对应口径。
  4. 假设用户切换查看范围,当切换生效,则与切换 commit 一致:不重置为加载占位(保留现有内容直到新数据到达)、展开的文件若在新范围中仍存在则保持展开、打开中的行评论框关闭且未提交草稿丢弃(见「差异面板行评论」场景 4)。
  5. 假设用户已选择某查看范围,当切换会话/工作目录或刷新触发,则保持当前范围选择(按会话记忆,与文件树显隐、选中 commit 同一套记忆机制),会话间不串。
  6. 假设仓库尚无任何 commit(无 HEAD),当面板打开,则「未提交改动」与「全部改动」等价(都相对已暂存内容),两者均可选择、可切换,不报错。

用户故事:差异面板提交选择(优先级:P2) ​

作为桌面端用户,我希望在差异面板里按 commit 逐个查看本次会话产生的提交,以便定位某个提交具体改了什么,而不是只能看累计的全部改动。

为什么是这个优先级:会话改动常被 agent 拆成多个 commit,累计 diff 难以对应到具体一步;逐 commit 查看是 claude.ai 差异面板的既有能力,数据可由宿主随改动数据一次取回,无需新增请求链路(P2)。

独立测试:让 agent 在会话分支产生两个 commit,打开差异面板验证提交列表(两条 commit + 段标题与条目数);点击某条 commit 验证面板只显示该提交的 diff、文件数/统计/文件树随之变化;切回「全部改动」验证恢复;无 commit 时验证不出现列表。

验收场景:

  1. 假设变更基准到 HEAD 之间存在 ≥1 个 commit,当面板打开,则面板提供提交选择(位于范围选择下方):以「提交」为段标题(附条目数,如 提交 2),按新→旧列出各 commit(短 sha + 提交标题);列表中不再包含范围项(「全部改动」由范围选择承载,见「差异面板查看范围」)。
  2. 假设用户选择某条 commit,当选择生效,则面板改为只展示该 commit 自身的改动(该提交与其父提交的差异;无父提交的根提交按整棵树的首次提交处理):文件数、总增删统计、文件树、手风琴内容均切换为该提交的改动;工具栏标题改为单提交标识(如 {短 sha},不再展示「→ 工作树」)。
  3. 假设用户从某条 commit 切回「全部改动」,当切换生效,则恢复变更基准 → 工作树的全部改动与标题;已展开的文件若在新范围内仍存在则保持展开,否则回落到该范围的默认展开规则。
  4. 假设变更基准到 HEAD 之间没有任何 commit(改动全在工作区未提交),当面板打开,则不渲染提交列表(范围选择仍照常提供,见「差异面板查看范围」),不得出现空列表占位或报错。
  5. 假设用户已选择某条 commit 作为查看范围,当切换会话/工作目录或刷新触发,则保持当前选择的提交;该提交在当前仓库已不存在(如 rebase 后)时回落到「全部改动」,不得展示错误态。

用户故事:差异面板文件树与导航(优先级:P2) ​

作为桌面端用户,我希望差异面板左侧有一棵可折叠的改动文件树,以便改动文件较多时快速定位并跳到关心的那个文件,而不是在长手风琴里滚动查找。

为什么是这个优先级:改动文件多时逐个头扫读成本高;文件树是 claude.ai 差异面板的主导航方式,且所需数据已在面板手中(沿用同一次改动数据,不新增请求)(P2)。

独立测试:造出多层目录的多个改动文件,验证文件树层级、目录折叠、文件行统计、测试文件默认不展开、显隐按钮;点击树中某文件验证手风琴展开该文件并滚动到可见;切换会话验证显隐状态记忆。

验收场景:

  1. 假设面板已加载改动,当查看面板,则左侧展示可折叠的文件树:按路径层级组织(目录可折叠/展开),每个文件行显示文件名与 +N −M 统计(完整路径在 title 提示中给出);节点顺序与手风琴中的文件顺序一致,仓库根目录本身不作为节点显示。
  2. 假设文件树已展示,当用户点击某个文件行,则手风琴展开该文件(互斥收起其它文件)并滚动到该文件可见;导航不改变手风琴的折叠语义(点击已展开的文件不产生额外展开态变化)。
  3. 假设改动文件包含测试文件(路径含 __tests__ / __snapshots__ / *.test.* / *.spec.* / test_*.py / *.snap),当面板首次加载选择默认展开文件,则默认展开的是第一个非测试文件(测试文件不自动展开);当范围内全部是测试文件时展开第一个。
  4. 假设面板已加载改动,当用户点击工具栏的文件树显隐按钮,则左侧栏(文件树与位于其下方的提交选择一并)收起(差异区占满面板宽度)或展开;显隐状态随会话面板记忆保留,切换会话、折叠面板再展开后原样恢复。
  5. 假设面板槽位宽度不足以并排容纳文件树与可用宽度的差异内容(小于 文件树宽度 240px + 差异区最小可读宽度),当查看面板,则左侧栏自动隐藏、差异区占满槽位;槽位加宽越过阈值即自动恢复显示——该自动降级不改写用户的显隐记忆状态。
  6. 假设用户选择了某条 commit 作为查看范围,当查看文件树,则文件树完整列出该范围的改动文件(统计取自该范围数据),并同样支持场景 2 的导航。

用户故事:差异视图切换与大规模差异降级(优先级:P2) ​

作为桌面端用户,我希望能在统一视图与并排视图之间切换,并且在差异特别大时面板不被拖垮(自动折叠全部文件并说明、对超长文件关掉逐词高亮),以便既能按习惯读 diff,又能在一次巨大改动下保持可用。

为什么是这个优先级:并排视图是 claude.ai 差异面板的既有读法;降级规则决定面板在大型改动(重命名整个目录、生成产物、minified 文件)下是否可用——这是本面板的可用性兜底(P2)。

独立测试:切换统一/并排视图验证同一 hunk 的删除/新增行左右对齐、双列行号、词级高亮仍生效、且切换不发出新的数据请求;构造总增删 > 5000 行的改动验证默认不展开任何文件并出现提示;构造单文件 > 1000 行的改动验证该文件无词级高亮但行级展示正常。

验收场景:

  1. 假设面板已加载改动,当用户点击工具栏的视图切换按钮,则在统一视图与并排视图之间切换:统一视图为上下排列的增删行 + 左侧单列行号,并排视图为左旧右新两列(含旧/新两列行号);默认统一视图(保持既有默认读法,不因本次改动改变既有用户打开面板时的外观)。
  2. 假设处于并排视图,当渲染某个 hunk,则 hunk 内按位置配对的删除行与新增行左右并排显示(左列 = 删除行,右列 = 新增行),配对不足的一侧补空行占位;上下文行左右两列同显;hunk 头横跨两列显示;左右两列不对同一行错位。
  3. 假设处于并排视图且某对删除/新增行已配对,当渲染,则词级高亮规则不变(变化的词以词级高亮标出,删除词附删除线):同一对行在并排两列中各自标出变化词,规则与统一视图一致。
  4. 假设用户触发视图切换,当切换完成,则不得重新向宿主请求改动数据(同一份 hunk 数据换一种排布);已展开的文件保持不变;展开中的行评论框关闭且未提交草稿丢弃(与刷新一致,见「差异面板行评论」场景 4)。
  5. 假设当前查看范围内的总增删行数超过 5000,当面板加载该数据,则默认不展开任何文件(覆盖「默认展开第一个非测试文件」),并在面板顶部显示说明(如「差异过大,已折叠全部文件」);用户仍可手动展开单个文件查看。
  6. 假设某文件的差异行数超过 1000,当该文件展开,则该文件不做词级配对高亮(只保留行级增删标识与 ± 标记),展示内容与其余交互(行评论、视图切换)不受影响;行数回到阈值以下后恢复正常词级高亮。
  7. 假设某文件的差异行数超过单文件内容上限(2000 行),当该文件展开,则按既有截断规则展示并提示「差异过大,已截断…」;三条规则的层级为:先按范围总行数决定是否默认折叠全部文件 → 再按单文件行数决定是否关词级高亮 → 最后按内容上限裁剪。

用户故事:差异面板行评论(优先级:P3) ​

作为用户,我希望在差异面板里右键某行(或选中若干连续行、点选区末尾浮出的评论按钮)展开输入框写下评论,评论连同文件路径与差异行上下文一起追加到消息输入框,以便我连续评论多行后统一发给 agent,agent 精准理解我指的是哪个文件的哪一处改动。

为什么是这个优先级:把「指哪改哪」闭环从原型预览扩展到代码 diff 审查;入口与 Claude 桌面端的 diff 面板一致(右键 添加评论 + 选中浮出的按钮),不为评论按钮预留常驻 gutter,行号与增删标记可以直接贴在这行最左侧;不改变差异面板只读属性。

独立测试:打开差异面板 → 右键某增/删行验证在指针位置弹出含「添加评论」的菜单 → 点击在行下展开评论框 → 输入评论并添加 → 验证评论连同文件路径与行内容追加到消息输入框(不直接发送)→ 在另一行重复添加第二条 → 验证两条评论都在输入框中,发送后聊天中出现含全部评论的用户消息;再选中连续若干行文本,验证选区末尾浮出评论按钮、点击后按行区间展开评论框。

验收场景:

  1. 假设差异面板已加载改动,当用户在代码区右键某行(增/删/上下文行,统一视图与并排视图下均适用),则在指针位置弹出菜单,其中一项为「添加评论」;点击后在该行下方展开行内评论框(含文本输入框与添加按钮),被评论行保持可见。行首不设常驻评论按钮、也不为它留 gutter:行号与增删标记直接贴在这行最左侧。
  2. 假设用户在代码区选中连续的若干行文本(跨行选区,统一视图与并排视图下均适用),当松开鼠标,则在选区末尾浮出评论按钮(不遮挡被选中的文字,滚动时跟着选区走);点击后按选区首末行展开评论框。选区被清除或点击别处后按钮消失;右键落在已有选区上时取选区首末行,而不是指针所在的那一行。
  3. 假设用户在评论框输入评论并点击添加按钮(或回车),则不得直接发送给 agent,必须将评论追加到消息输入框中,追加内容包含:文件路径、差异行内容(或片段)、评论文本;输入框已有内容时必须保留并在其后追加;评论框关闭,由用户在输入框中统一编辑后手动发送,发送后的消息呈现与普通用户消息一致。
  4. 假设评论框已展开,当用户按 Esc 或点击取消,则评论框关闭且不提交;用户可继续右键或选中其他行添加评论,无需开关任何模式即可连续评论多行。
  5. 假设面板发生刷新(自动/手动)、切换会话/工作目录、切换提交范围或切换统一/并排视图,则展开中的评论框必须关闭、未提交的草稿丢弃,防止行内容变化后界面状态不一致。

边界情况 ​

  • 被评论行随后被刷新覆盖:差异行评论携带提交时刻的上下文快照(文件路径与行内容);刷新后行内容/行序变化不构成错误,后续评论需重新悬停添加。

  • 差异面板 diff 输出过大:单文件 diff 超大(如 minified 文件)时必须截断展示并提示,不得阻塞渲染(降级规则的完整层级见「差异视图切换与大规模差异降级」场景 7)。

  • 变更基准解析失败或退化:解析不到仓库默认分支(无 origin、无 main/master)或分叉点计算失败(无共同祖先)时,变更基准退化为 HEAD——面板只显示未提交改动,不报错、不展示空面板;解析顺序与退化条件见「会话变更差异面板」场景 2。

  • 会话分支即默认分支:分叉点等于 HEAD,面板行为与退化时一致(只显示未提交改动),用户不应因此看到空面板或错误提示。

  • rebase / 重置后基准变化:变更基准随当前分支状态重新计算,刷新后按新基准展示;已选择的单条 commit 范围在当前仓库中不再存在时回落到「全部改动」(见「差异面板提交选择」场景 5),查看范围选择本身不受影响。

  • 会话分支的改动已被合并进默认分支:--rebase 一类合并把提交改写成新 sha 后,那枚原提交不再是默认分支的祖先,分叉点基线不会前移,「全部改动」范围会继续显示这批已合并的改动——这是该口径的定义(面板不判断「是否已合并」);出口是切到「未提交改动」,而不是等待面板自动归零(见「差异面板查看范围」)。

  • 处于 merge / rebase / cherry-pick / revert 中间状态:面板视空并说明原因,不把待并入的改动列成用户改动;恢复不在中间状态后自然回到正常展示(见「会话变更差异面板」场景 13)。

  • 未跟踪文件与查看范围的关系:未跟踪文件属于工作树,出现在「全部改动」与「未提交改动」两个范围;切换到单条 commit 范围时不出现。

  • 变更基准名过长:工具栏标题按可用宽度截断,优先保证「→ 工作树」一侧可读;完整值在 title 提示中给出。

非目标(明确排除) ​

  • 差异面板的写操作:首版只读,不提供暂存、提交、丢弃改动、逐行回退等操作。

  • 差异面板的任意 ref 对比:只提供「全部改动」「未提交改动」与「单个 commit」三种查看范围;不提供 base/head 选择器,不支持任选两个分支/tag/commit 对比,也不把变更基准本身做成可改选项。

  • 差异面板判断改动「是否已合并」:不实现 patch-id 比对一类的合并检测(脆弱且非用户诉求);已合并的改动是否还显示取决于用户选择的查看范围。

  • 差异面板的补丁导出与应用:不做下载 patch、应用补丁等操作(面板始终只读,写操作边界见「差异面板的写操作」)。

  • 全文件同时展开的虚拟化列表:保持「同一时刻至多展开一个文件」的手风琴模型,不做全部文件同时展开的堆叠卡片与虚拟化渲染。